Kubernetes学习笔记
- 1: 云原生体系
- 2: 安装与配置
- 2.1: 部署k8s集群
- 2.1.1: 使用kubeadm部署生产环境kubernetes集群
- 2.1.2: 使用kubespray安装kubernetes
- 2.1.3: 使用minikube安装kubernetes
- 2.1.4: 使用kind安装kubernetes
- 2.1.5: 安装k8s dashboard
- 2.2: kubeadm升级k8s集群
- 2.3: k8s证书及秘钥
- 2.4: kubeadm管理证书
- 2.5: k8s版本说明
- 2.6: k8s版本记录
- 3: 基本概念
- 3.1: kubernetes架构
- 3.1.1: Kubernetes总架构图
- 3.1.2: 基于Docker及Kubernetes技术构建容器云(PaaS)平台
- 3.2: kubernetes对象
- 3.2.1: Kubernetes基本概念
- 3.2.2: 理解kubernetes对象
- 3.3: Pod对象
- 3.3.1: Pod介绍
- 3.3.2: Pod定义文件
- 3.3.3: Pod生命周期
- 3.3.4: Pod健康检查
- 3.3.5: Pod存储卷
- 3.3.6: Pod调度
- 3.3.7: Pod伸缩与升级
- 3.4: 配置
- 3.4.1: ConfigMap
- 4: 核心原理
- 4.1: 核心组件
- 4.1.1: Kubernetes核心原理(一)之API Server
- 4.1.2: Kubernetes核心原理(二)之Controller Manager
- 4.1.3: Kubernetes核心原理(三)之Scheduler
- 4.1.4: Kubernetes核心原理(四)之kubelet
- 4.2: 流程图
- 5: 容器网络
- 5.1: Docker网络
- 5.2: K8S网络
- 5.3: Pod的DNS策略
- 5.4: CNI
- 5.5: Flannel
- 5.6: Calico
- 5.6.1: Calico介绍
- 5.7: Cilium
- 5.7.1: Cilium介绍
- 5.8: k8s网关
- 5.8.1: 安装APISIX
- 5.8.2: 创建路由
- 5.8.3: ingress-controller原理
- 5.8.4: APISIX配置
- 6: 容器存储
- 6.1: 存储卷概念
- 6.1.1: Volume介绍
- 6.1.2: PersistentVolume 介绍
- 6.1.3: PersistentVolumeClaim 介绍
- 6.1.4: StorageClass 介绍
- 6.1.5: Dynamic Volume Provisioning 介绍
- 6.2: CSI
- 6.2.1: csi-cephfs-plugin
- 6.2.2: 部署csi-cephfs
- 6.2.3: 部署cephfs-provisioner
- 6.2.4: FlexVolume介绍
- 7: 资源隔离
- 7.1: 资源配额
- 7.2: 资源配额
- 7.3: 资源服务质量
- 7.4: Lxcfs资源视图隔离
- 8: 运维指南
- 8.1: Kubernetes集群问题排查
- 8.2: kubectl工具
- 8.2.1: kubectl安装与配置
- 8.2.2: kubectl命令使用
- 8.2.3: kubectl命令别名
- 8.2.4: kubectl进入node shell
- 8.2.5: kubeconfig的使用
- 8.3: 节点迁移
- 8.4: 镜像仓库
- 8.5: 版本发布
- 8.5.1: 金丝雀发布
- 8.5.2: Kruise Rollout发布
- 8.5.3: HPA[自动扩缩容]配置
- 8.6: helm工具
- 8.7: 访问控制
- 8.7.1: 使用 RBAC 鉴权
- 9: 开发指南
- 9.1: client-go的使用及源码分析
- 9.2: operator开发
- 9.2.1: kubebuilder的使用
- 9.2.2: 如何开发一个Operator
- 9.3: CSI插件开发
- 9.3.1: csi-provisioner源码分析
- 9.3.2: nfs-client-provisioner源码分析
- 9.4: k8s社区开发指南
- 10: 问题排查
- 10.1: 节点问题
- 10.1.1: keycreate permission denied
- 10.1.2: Cgroup不支持pid资源
- 10.1.3: Cgroup子系统无法挂载
- 10.1.4: runc-v1.1.3-exec-failed
- 10.1.5: increase the mlock limit
- 10.1.6: Kubelet初始化QOS失败
- 10.2: Pod驱逐
- 10.3: 镜像拉取失败问题
- 10.4: PVC Terminating
- 10.5: ConfigMap多行格式
- 11: Runtime
- 11.1: Runc和Containerd概述
- 11.2: Namespace
- 11.2.1: Namespace介绍
- 11.2.2: Namespace命令介绍
- 11.3: Cgroup
- 11.3.1: Cgroup介绍
- 11.3.2: Cgroup命令介绍
- 11.3.3: Cgroup目录
- 11.3.4: Cgroup v2和v1的区别
- 11.4: Containerd
- 11.4.1: 安装Containerd
- 11.4.2: Containerd命令工具
- 11.4.3: 移除Dockershim
- 11.5: Docker
- 11.5.1: 安装Docker
- 11.5.2: Docker整体架构图
- 11.5.3: Docker常用命令原理图
- 11.5.4: Dockerfile使用说明
- 11.6: Kata Container
- 11.7: WasmEdge介绍
- 12: Etcd集群
- 12.1: Etcd介绍
- 12.2: 部署Etcd集群
- 12.2.1: 使用etcdadm部署Etcd集群
- 12.3: Raft算法
- 12.4: etcdctl命令工具
- 12.4.1: etcdctl-V3
- 12.4.2: etcdctl-V2
- 12.5: Etcd访问控制
- 12.6: Etcd启动配置参数
- 12.7: Etcd中的k8s数据
- 12.8: etcd-operator的使用
- 13: 多集群管理
- 13.1: k8s多集群管理的思考
- 13.2: Virtual Kubelet
- 13.2.1: Virtual Kubelet介绍
- 13.2.2: Virtual Kubelet命令
- 13.3: Karmada
- 13.3.1: Karmada介绍
- 14: 边缘容器
- 14.1: KubeEdge
- 14.1.1: KubeEdge介绍
- 14.1.2: KubeEdge源码分析
- 14.1.2.1: Kubeedge之cloudcore 源码分析
- 14.1.2.2: Kubeedge之edgecore 源码分析
- 14.2: OpenYurt
- 14.2.1: OpenYurt部署
- 14.2.2: OpenYurt 安装相关Kubernetes配置调整
- 14.2.3: OpenYurt源码分析
- 14.2.3.1: OpenYurt之YurtHub源码分析
- 14.2.3.2: OpenYurt之TunnelServer源码分析
- 14.2.3.3: OpenYurt之Tunnel-Agent源码分析
- 15: 虚拟化
- 15.1: 虚拟化相关概念
- 15.2: KubeVirt
- 15.2.1: KubeVirt的介绍
- 15.2.2: KubeVirt的使用
- 15.3: qemu创建虚拟机
- 16: 监控体系
- 16.1: kube-prometheus-stack的使用
- 16.2: Kubernetes集群监控
- 16.3: cAdvisor介绍
- 16.4: Heapster介绍
- 16.5: Influxdb介绍
- 16.6: Grafana部署
- 17: Serverless
- 17.1: IaC
- 17.1.1: Terraform的使用
- 17.1.2: Terraform的基本概念
- 17.2: knative介绍
- 18: 集群优化
- 18.1: 调度优化
- 18.1.1: 大规模Pod调度优化
- 18.1.2: Volcano的使用
- 18.2: GPU
- 18.2.1: k8s管理GPU容器
- 18.2.2: Volcano GPU虚拟化
1 - 云原生体系
1.1 - k8s知识体系
1. k8s知识体系
以下整理了k8s涉及的相关知识体系。

思维导图:k8s体系
2. k8s重点开源项目
| 大类 | 小类 | 项目及链接 | 简介 |
|---|---|---|---|
| 🧭 核心调度与资源管理 | 核心调度器 | kube-scheduler | 默认调度器,支持亲和性、优先级等策略 |
| 批处理调度 | Volcano | 支持 AI 训练、大数据任务 Gang 调度、队列管理 | |
| 混部调度 | Koordinator | 支持离在线混部、QoS 管理、NUMA 亲和等能力 | |
| 多集群调度 | Karmada | 多集群资源调度和统一控制 | |
| Pod 优化调度 | Descheduler | 定期检测资源不均衡并重新调度 | |
| 🌐 网络与服务网格 | 网络插件(CNI) | Calico Cilium Flannel |
Pod 网络连接,Cilium 基于 eBPF 支持 L7 策略 |
| 服务网格 | Istio Linkerd |
微服务通信控制:认证、流量治理、可观测性 | |
| DNS 服务 | CoreDNS | Kubernetes 默认 DNS 插件 | |
| LoadBalancer | MetalLB | 裸金属集群中提供 LoadBalancer 类型服务 | |
| Gateway 网关 | Apache APISIX Ingress Kong Ingress NGINX Ingress Envoy Gateway |
支持 Ingress/Gateway API,提供流量入口、认证、限流等 | |
| 🔐 安全与策略控制 | 策略控制 | Kyverno Gatekeeper |
对资源进行策略校验、合规控制 |
| 密钥管理 | VaultSealed Secrets | 管理和加密存储 Kubernetes 密钥和凭据 | |
| 运行时安全 | Falco | 实时监控容器中可疑行为 | |
| 镜像签名 | cosign | 为容器镜像提供签名与验证功能 | |
| 💾 存储与数据保护 | CSI 存储 | Longhorn Rook |
提供持久化块存储或对象存储能力 |
| 备份与恢复 | Velero | Pod 和卷的备份、恢复、集群迁移 | |
| 卷调度组件 | external-provisioner | Kubernetes 官方 CSI 卷调度器 | |
| 📊 监控与可观测性 | 指标采集 | Prometheus kube-state-metrics |
收集节点/Pod 状态与业务指标 |
| 可视化展示 | Grafana | 指标、日志、链路追踪可视化 | |
| 日志系统 | Loki Fluent Bit |
日志采集与聚合,低资源占用 | |
| 链路追踪 | Jaeger OpenTelemetry |
追踪服务调用链路、性能瓶颈 | |
| 🧪 CI/CD 与 GitOps | 工作流引擎 | Argo Workflows Tekton |
原生工作流与流水线管理 |
| GitOps | Argo CDFlux | Git 驱动的自动部署与管理 | |
| 镜像构建 | Kaniko BuildKit |
无需 Docker daemon 的镜像构建工具 | |
| 应用包管理 | Helm | Kubernetes 最主流的应用部署工具,支持模板化部署 | |
| ⚙️ 集群运维与扩展 | 快速部署 | kubeadm kind k3s |
轻量化和本地环境 Kubernetes 集群安装工具 |
| 节点扩缩容 | Cluster Autoscaler | 根据 Pod 排队与资源使用率自动扩缩容节点 | |
| 多集群互联 | Submariner | 跨集群的网络互通方案 | |
| 故障检测 | Node Problem Detector | 检测节点硬件、系统异常并上报 K8s | |
| 混沌测试 | Chaos Mesh Litmus |
注入网络/CPU/磁盘等故障模拟场景 | |
| Workload 扩展 | OpenKruise | 扩展原生 workload 控制器能力,如 Sidecar、InPlace 升级 | |
| 应用交付平台 | KubeVela | 基于 OAM 模型,提供开发者友好、平台团队可控的应用交付能力 | |
| 🤖 AI 与大数据 | AI 平台 | Kubeflow | 支持训练、调度、模型管理的机器学习平台 |
| 联邦学习 | KubeFATE | 基于 K8s 的联邦学习调度与部署平台 | |
| 模型推理 | Triton Inference Server vLLM |
高性能大模型/AI 模型推理服务 | |
| 分布式调度器 | Volcano | 支持 AI 大数据任务的 batch 资源调度 | |
| ⚡ GPU 与硬件加速 | GPU 驱动插件 | NVIDIA device plugin | 为 K8s 提供 GPU 发现、分配与隔离能力 |
| GPU 监控工具 | DCGM Exporter | GPU 状态指标(温度、利用率、内存)采集器,用于 Prometheus | |
| GPU 虚拟化 | vGPU manager | 管理 GPU 分片、vGPU 分配 | |
| GPU 调度增强 | Koordinator Volcano |
支持 NUMA-aware、GPU topology-aware 的任务调度策略 |
1.2 - 12 Factor
以下主要介绍
PaaS平台设计架构中使用到的方法论,统称为12-Factor(要素)
简介
软件通常会作为一种服务来交付,即软件即服务(SaaS)。12-Factor原则为构建SaaS应用提供了以下的方法论:
- 使用标准化流程自动配置,减少开发者的学习成本。
- 和操作系统解耦,使其可以在各个系统间提供最大的移植性。
- 适合部署在现代的云计算平台上,从而在服务器和系统管理方面节省资源。
- 将开发环境与生产环境的差异降至最低,并使用持续交付实施敏捷开发。
- 可以在工具、架构和开发流程不发生明显变化的前提下实现拓展
该理论适应于任何语言和后端服务(数据库、消息队列、缓存等)开发的应用程序。
1. 基准代码
一份基准代码,多份部署
应用代码使用版本控制系统来管理,常用的有Git、SVN等。一份用来跟踪代码所有修订版本的数据库称为代码库。
1.1. 一份基准代码
基准代码和应用之间总是保持一一对应的关系:
- 一旦有多个基准代码,则不能称之为一个应用,而是一个分布式系统。分布式系统中的每个组件都是一个应用,每个应用都可以使用
12-Factor原则进行开发。 - 多个应用共享一份基准代码有悖于
12-Factor原则。解决方法是将共享的代码拆成独立的类库,通过依赖管理去使用它们。
1.2. 多份部署
每个应用只对应一份基准代码,但可以同时存在多份的部署,每份部署相当于运行了一个应用的实例。
多份部署的区别在于:
- 可以存在不同的配置文件对应不同的环境。例如开发环境、预发布环境、生产环境等。
- 可以使用不同的版本。例如开发环境的版本可能高于预发布环境版本,还没同步到预发布环境版本,同理,预发布环境版本可能高于生产环境版本。
2. 依赖
显式声明依赖关系
大多数的编程语言都会提供一个包管理系统或工具,其中包含所有的依赖库,例如Golang的vendor目录存放了该应用的所有依赖包。
12-Factor原则下的应用会通过依赖清单来显式确切地声明所有的依赖项。在运行工程中通过依赖隔离工具来保证应用不会去调用系统中存在但依赖清单中未声明的依赖项。
显式声明依赖项的优点在于可以简化环境配置流程,开发者关注应用的基准代码,而依赖库则由依赖库管理工具来管理和配置。例如,Golang中的包管理工具dep等。
3. 配置
在环境中存储配置
通常,应用的配置在不同的发布环境中(例如:开发、预发布、生产环境)会有很大的差异,其中包括:
- 数据库、Redis等后端服务的配置
- 每份部署特有的配置,例如域名
- 第三方服务的证书等
12-Factor原则要求代码和配置严格分离,而不应该通过代码常量的形式写在代理里面。配置在不同的部署环境中存在大幅差异,但是代码却是完全一致的。
判断一个应用是否正确地将配置排除在代码外,可以看应用的基准代码是否可以立即开源而不担心暴露敏感信息。
12-Factor原则建议将应用的配置存储在环境变量中,环境变量可以方便在不同的部署环境中修改,而不侵入原有的代码。(例如,k8s的大部分代码配置是通过环境变量的方式来传入的)。
12-Factor应用中,环境变量的粒度要足够小且相对独立。当应用需要拓展时,可以平滑过渡。
4. 后端服务
把后端服务当作附加资源
后端服务指程序运行时所需要通过网络调用的各种服务,例如:数据库(MySQL,CouchDB),消息/队列系统(RabbitMQ,Beanstalkd),SMTP 邮件发送服务(Postfix),以及缓存系统(Memcached)。
其中可以根据管理对象分为本地服务(例如本地数据库)和第三方服务(例如Amason S3)。对于12-Factor应用来说都是附加资源,没有区别对待,当其中一份后端服务失效后,可以通过切换到原先备份的后端服务中,而不需要修改代码(但可能需要修改配置)。12-Factor应用与后端服务保持松耦合的关系。
5. 构建,发布,运行
严格分离构建和运行
基准代码转化成一份部署需要经过三个阶段:
- 构建阶段:指代码转化为可执行包的过程。构建过程会使用指定版本的代码,获取依赖项,编译生成二进制文件和资源文件。
- 发布阶段:将构建的结果与当前部署所需的配置结合,并可以在运行环境中使用。
- 运行阶段(运行时):指针对指定的发布版本在执行环境中启动一系列应用程序的进程。
12-Factor应用严格区分构建、发布、运行三个步骤,每一个发布版本对应一个唯一的发布ID,可以使用时间戳或递增的版本序列号。
如果需要修改则需要产生一个新的发布版本,如果需要回退,则回退到之前指定的发布版本。
新代码部署之前,由开发人员触发构建操作,构建阶段可以相对复杂一些,方便错误信息可以展示出来得到妥善处理。运行阶段可以人为触发或自动运行,运行阶段应该保持尽可能少的模块。
6. 进程
以一个或多个无状态进程运行应用
12-Factor应有的进程必须是无状态且无共享的,任何需要持久化的数据存储在后端服务中,例如数据库。
内存区域和磁盘空间可以作为进程的缓存,12-Factor应用不需要关注这些缓存的持久化,而是允许其丢失,例如重启的时候。
进程的二进制文件应该在构建阶段执行编译而不是运行阶段。
当应用使用到粘性Session,即将用户的session数据缓存到进程的内存中,将同一用户的后续请求路由到同一个进程。12-Factor应用反对这种处理方式,而是建议将session的数据保存在redis/memcached带有过期时间的缓存中。
7. 端口绑定
通过端口绑定提供服务
应用通过端口绑定来提供服务,并监听发送至该端口的请求。端口绑定的方式意味着一个应用也可以成为另一个应用的后端服务,例如提供某些API请求。
8. 并发
通过进程模型进行扩展
12-Factor应用中,开发人员可以将不同的工作分配给不同类型进程,例如HTTP请求由web进程来处理,常驻的后台工作由worker进程来处理(k8s的设计中就经常用不同类型的manager来处理不同的任务)。
12-Factor应用的进程具备无共享、水平分区的特性,使得水平扩展较为容易。
12-Factor应用的进程不需要守护进程或是写入PID文件,而是通过进程管理器(例如 systemd)来管理输出流,响应崩溃的进程,以及处理用户触发的重启或关闭超级进程的操作。
9. 易处理
快速启动和优雅终止可最大化健壮性
12-Factor应用的进程是易处理的,即它们可以快速的开启或停止,这样有利于快速部署迭代和弹性伸缩实例。
进程应该追求最小的启动时间,这样可以敏捷发布,增加健壮性,当出现问题可以快速在别的机器部署一个实例。
进程一旦接收到终止信号(SIGTERM)就会优雅终止。优雅终止指停止监听服务的端口,拒绝所有新的请求,并继续执行当前已接收的请求,然后退出。
进程还需在面对突然挂掉的情况下保持健壮性,例如通过任务队列的方式来解决进程突然挂掉而没有完成处理的事情,所以应该设计为任务执行是幂等的,可以被重复执行,重复执行的结果是一致的。
10. 开发环境与线上环境等价
尽可能的保持开发,预发布,线上环境相同
不同的发布环境可能存在以下差异:
- 时间差异:开发到部署的周期较长。
- 人员差异:开发人员只负责开发,运维人员只负责部署。分工过于隔离。
- 工具差异:不同环境的配置和运行环境,使用的后端类型可能存在不同。
应尽量缩小本地与线上的差异,缩短上线周期,开发运维一体化,保证开发环境与线上运行的环境一致(例如,可以通过Docker容器的方式)。
11. 日志
把日志当作事件流
日志应该是事件流的汇总。12-Factor应用本身不考虑存储自己的日志输出流,不去写或管理日志文件,而是通过标准输出(stdout)的方式。
日志的标准输出流可以通过其他组件截获,整合其他的日志输出流,一并发给统一的日志中心处理,用于查看或存档。例如:日志收集开源工具Fluentd。
截获的日志流可以输出至文件,或者在终端实时查看。最重要的是可以发送到Splunk这样的日志索引及分析系统,提供后续的分析统计及监控告警等功能。例如:
- 找出过去一段时间的特殊事件。
- 图形化一个大规模的趋势,如每分钟的请求量。
- 根据用户定义的条件触发告警,如每分钟报错数超过某个警戒线。
12. 管理进程
后台管理任务当作一次性进程运行
开发人员经常需要执行一些管理或维护应用的一次性任务,一次性管理进程应该和常驻进程使用相同的运行环境,开发人员可以通过ssh方式来执行一次性脚本或任务。
参考:
2 - 安装与配置
2.1 - 部署k8s集群
2.1.1 - 使用kubeadm部署生产环境kubernetes集群
本文为基于
kubeadm搭建生产环境级别高可用的k8s集群。
1. 环境准备
1.0. master硬件配置
参考:
Kubernetes集群Master节点上运行着etcd、kube-apiserver、kube-controller等核心组件,对于Kubernetes集群的稳定性有着至关重要的影响,对于生产环境的集群,必须慎重选择Master规格。Master规格跟集群规模有关,集群规模越大,所需要的Master规格也越高。
说明 :可从多个角度衡量集群规模,例如节点数量、Pod数量、部署频率、访问量。这里简单的认为集群规模就是集群里的节点数量。
对于常见的集群规模,可以参见如下的方式选择Master节点的规格(对于测试环境,规格可以小一些。下面的选择能尽量保证Master负载维持在一个较低的水平上)。
| 节点规模 | Master规格 | 磁盘 |
|---|---|---|
| 1~5个节点 | 4核8 GB(不建议2核4 GB) | |
| 6~20个节点 | 4核16 GB | |
| 21~100个节点 | 8核32 GB | |
| 100~200个节点 | 16核64 GB | |
| 1000个节点 | 32核128GB | 1T SSD |
注意事项:
-
由于Etcd的性能瓶颈,Etcd的数据存储盘尽量选择SSD磁盘。
-
为了实现多机房容灾,可将三台master分布在一个可用区下三个不同机房。(机房之间的网络延迟在10毫秒及以下级别)
-
申请LB来做master节点的负载均衡实现高可用,LB作为apiserver的访问地址。
1.1. 设置防火墙端口策略
生产环境设置k8s节点的iptables端口访问规则。
1.1.1. master节点端口配置
| 协议 | 方向 | 端口范围 | 目的 | 使用者 |
|---|---|---|---|---|
| TCP | 入站 | 6443 | Kubernetes API server | 所有 |
| TCP | 入站 | 2379-2380 | etcd server client API | kube-apiserver, etcd |
| TCP | 入站 | 10250 | Kubelet API | 自身, 控制面 |
| TCP | 入站 | 10259 | kube-scheduler | 自身 |
| TCP | 入站 | 10257 | kube-controller-manager | 自身 |
1.1.2. worker节点端口配置
| 协议 | 方向 | 端口范围 | 目的 | 使用者 |
|---|---|---|---|---|
| TCP | 入站 | 10250 | Kubelet API | 自身, 控制面 |
| TCP | 入站 | 30000-32767 | NodePort Services | 所有 |
添加防火墙iptables规则
master节点开放6443、2379、2380端口。
iptables -A INPUT -p tcp -m multiport --dports 6443,2379,2380,10250 -j ACCEPT
1.2. 关闭swap分区
[root@master ~]#swapoff -a
[root@master ~]#
[root@master ~]# free -m
total used free shared buff/cache available
Mem: 976 366 135 6 474 393
Swap: 0 0 0
# swap 一栏为0,表示已经关闭了swap
1.3. 开启br_netfilter和bridge-nf-call-iptables
参考:https://imroc.cc/post/202105/why-enable-bridge-nf-call-iptables/
# 设置加载br_netfilter模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 开启bridge-nf-call-iptables ,设置所需的 sysctl 参数,参数在重新启动后保持不变
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# 应用 sysctl 参数而不重新启动
sudo sysctl --system
2. 安装容器运行时
在所有主机上安装容器运行时,推荐使用containerd为runtime。以下分别是containerd与docker的安装命令。
2.1. Containerd
1、参考:安装containerd
# for ubuntu
apt install -y containerd.io
2、生成默认配置
containerd config default > /etc/containerd/config.toml
3、修改CgroupDriver为systemd
k8s官方推荐使用systemd类型的CgroupDriver。
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
4、重启containerd
systemctl restart containerd
2.2. Docker
# for ubuntu
apt install -y docker.io
官方建议配置cgroupdriver为systemd。
# 修改docker进程管理器
vi /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
systemctl daemon-reload && systemctl restart docker
docker info | grep -i cgroup
2.3. Container Socket
| 运行时 | Unix 域套接字 |
|---|---|
| Containerd | unix:///var/run/containerd/containerd.sock |
| CRI-O | unix:///var/run/crio/crio.sock |
| Docker Engine (使用 cri-dockerd) | unix:///var/run/cri-dockerd.sock |
3. 安装kubeadm,kubelet,kubectl
安装脚本可参考仓库:https://github.com/huweihuang/kubeadm-scripts.git
在所有主机上安装kubeadm,kubelet,kubectl。最好版本与需要安装的k8s的版本一致。
# 以Ubuntu系统为例
# 安装仓库依赖
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
# use google registry
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
# or use aliyun registry
curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -
tee /etc/apt/sources.list.d/kubernetes.list <<EOF
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
# 安装指定版本的kubeadm, kubelet, kubectl
apt-get update
apt-get install -y kubelet=1.24.2-00 kubeadm=1.24.2-00 kubectl=1.24.2-00
# 查询有哪些版本
apt-cache madison kubeadm
离线下载安装
#!/bin/bash
Version=${Version:-1.24.2}
wget https://dl.k8s.io/release/v${Version}/bin/linux/amd64/kubeadm
wget https://dl.k8s.io/release/v${Version}/bin/linux/amd64/kubelet
wget https://dl.k8s.io/release/v${Version}/bin/linux/amd64/kubectl
chmod +x kubeadm kubelet kubectl
cp kubeadm kubelet kubectl /usr/bin/
# add kubelet serivce
cat > /lib/systemd/system/kubelet.service << EOF
[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/home/
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
mkdir -p /etc/systemd/system/kubelet.service.d
cat > /etc/systemd/system/kubelet.service.d/10-kubeadm.conf << \EOF
# Note: This dropin only works with kubeadm and kubelet v1.11+
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
EnvironmentFile=-/etc/default/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
EOF
systemctl daemon-reload
systemctl enable kubelet
systemctl restart kubelet
4. 配置kubeadm config
参考:
4.1. 配置项说明
4.1.1. 配置类型
kubeadm config支持以下几类配置。
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
apiVersion: kubeadm.k8s.io/v1beta3
kind: JoinConfiguration
可以使用以下命令打印init和join的默认配置。
kubeadm config print init-defaults
kubeadm config print join-defaults
4.1.2. Init配置
kubeadm init配置中只有InitConfiguration 和 ClusterConfiguration 是必须的。
InitConfiguration:
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
bootstrapTokens:
...
nodeRegistration:
...
- bootstrapTokens
- nodeRegistration
- criSocket:runtime的socket
- name:节点名称
- localAPIEndpoint
- advertiseAddress:apiserver的广播IP
- bindPort:k8s控制面安全端口
ClusterConfiguration:
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
networking:
...
etcd:
...
apiServer:
extraArgs:
...
extraVolumes:
...
...
-
networking:
- podSubnet:Pod CIDR范围
- serviceSubnet: service CIDR范围
- dnsDomain
-
etcd:
- dataDir:Etcd的数据存储目录
-
apiserver
- certSANs:设置额外的apiserver的域名签名证书
-
imageRepository:镜像仓库
-
controlPlaneEndpoint:控制面LB的域名
-
kubernetesVersion:k8s版本
4.2. Init配置示例
在master节点生成默认配置,并修改配置参数。
kubeadm config print init-defaults > kubeadm-config.yaml
修改配置内容
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 1.2.3.4 # 修改为apiserver的IP 或者去掉localAPIEndpoint则会读取默认IP。
bindPort: 6443
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: node
taints: null
---
apiServer:
certSANs:
- lb.k8s.domain # 添加额外的apiserver的域名
- <vip/lb_ip>
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {} # 默认为coredns
etcd:
local:
dataDir: /data/etcd # 修改etcd的存储盘目录
imageRepository: k8s.gcr.io # 修改镜像仓库地址
controlPlaneEndpoint: lb.k8s.domain # 修改控制面域名
kind: ClusterConfiguration
kubernetesVersion: 1.24.0 # k8s 版本
networking:
dnsDomain: cluster.local
serviceSubnet: 10.96.0.0/12
podSubnet: 10.244.0.0/16 # 设置pod的IP范围
scheduler: {}
---
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd # 设置为systemd
安装完成后可以查看kubeadm config
kubectl get cm -n kube-system kubeadm-config -oyaml
5. 安装Master控制面
提前拉取镜像:
kubeadm config images pull
5.1. 安装master
sudo kubeadm init --config kubeadm-config.yaml --upload-certs --node-name <nodename>
部署参数说明:
-
--control-plane-endpoint:指定控制面(kube-apiserver)的IP或DNS域名地址。
-
--apiserver-advertise-address:kube-apiserver的IP地址。
-
--pod-network-cidr:pod network范围,控制面会自动给每个节点分配CIDR。
-
--service-cidr:service的IP范围,default "10.96.0.0/12"。
-
--kubernetes-version:指定k8s的版本。
-
--image-repository:指定k8s镜像仓库地址。
-
--upload-certs :标志用来将在所有控制平面实例之间的共享证书上传到集群。
-
--node-name:hostname-override,作为节点名称。
执行完毕会输出添加master和添加worker的命令如下:
...
You can now join any number of control-plane node by running the following command on each as a root:
kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 --control-plane --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use kubeadm init phase upload-certs to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
5.2. 添加其他master
添加master和添加worker的差别在于添加master多了--control-plane 参数来表示添加类型为master。
kubeadm join <control-plane-endpoint>:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane --certificate-key <certificate-key> \
--node-name <nodename>
6. 添加Node节点
kubeadm join <control-plane-endpoint>:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--cri-socket /run/containerd/containerd.sock \
--node-name <nodename>
7. 安装网络插件
## 如果安装之后node的状态都改为ready,即为成功
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
kubectl apply -f ./kube-flannel.yml
kubectl get nodes
如果Pod CIDR的网段不是10.244.0.0/16,则需要加flannel配置中的网段更改为与Pod CIDR的网段一致。
7.1. 问题
Warning FailedCreatePodSandBox 4m6s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "300d9b570cc1e23b6335c407b8e7d0ef2c74dc2fe5d7a110678c2dc919c62edf": plugin type="flannel" failed (add): failed to delegate add: failed to set bridge addr: "cni0" already has an IP address different from 10.244.3.1/24
原因:
宿主机节点有cni0网卡,且网卡的IP段与flannel的CIDR网段不同,因此需要删除该网卡,让其重建。
查看cni0网卡
# ifconfig cni0 |grep -w inet
inet 10.244.5.1 netmask 255.255.255.0 broadcast 10.244.116.255
查看flannel配置
# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.116.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
发现cni0 IP与FLANNEL_SUBNET网段不一致,因此删除cni0重建。
解决:
ifconfig cni0 down
ip link delete cni0
8. 部署dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.0/aio/deploy/recommended.yaml
镜像: kubernetesui/dashboard:v2.5.0
默认端口:8443
登录页面需要填入token或kubeconfig

参考:dashboard/creating-sample-user
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
创建用户
kubectl -n kubernetes-dashboard create token admin-user
9. 重置部署
# kubeadm重置
kubeadm reset
# 清空数据目录
rm -fr /data/etcd
rm -fr /etc/kubernetes
rm -fr ~/.kube/
删除flannel
ifconfig cni0 down
ip link delete cni0
ifconfig flannel.1 down
ip link delete flannel.1
rm -rf /var/lib/cni/
rm -f /etc/cni/net.d/*
10. 问题排查
10.1. kubeadm token过期
问题描述:
添加节点时报以下错误:
[discovery] The cluster-info ConfigMap does not yet contain a JWS signature for token ID "abcdef", will try again
原因:token过期,初始化token后会在24小时候会被master删除。
解决办法:
# 重新生成token
kubeadm token create --print-join-command
kubeadm token list
# kubeadm token create
oumnnc.aqlxuvdbntlvzoiv
# 重新生成hash
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
基于新生成的token重新添加节点。
10.2. 修改kubeadm join的master IP或端口
kubeadm join命令会去kube-public命名空间获取名为cluster-info的ConfigMap。如果需要修改kubeadm join使用的master的IP或端口,则需要修改cluster-info的configmap。
# 查看cluster-info
kubectl -n kube-public get configmaps cluster-info -o yaml
# 修改cluster-info
kubectl -n kube-public edit configmaps cluster-info
修改配置文件中的server字段
clusters:
- cluster:
certificate-authority-data: xxx
server: https://lb.k8s.domain:36443
name: ""
执行kubeadm join的命令时指定新修改的master地址。
10.3. conntrack not found
[preflight] Some fatal errors occurred:
[ERROR FileExisting-conntrack]: conntrack not found in system path
解决方法:
apt -y install conntrack
10.4. Kubelet: unable to determine runtime API version
Error: failed to run Kubelet: unable to determine runtime API version: rpc error:code = Unavailable desc = connection error: desc = "transport: Error while dialing dial unix: missing address"
解决方法:
检查kubelet的启动参数,可以用二进制直接添加参数debug
# 查看启动参数是否遗漏,比如10-kubeadm.conf 文件参数缺失
systemctl cat --no-pager kubelet
cat /lib/systemd/system/kubelet.service
cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
参考:
- 利用 kubeadm 创建高可用集群 | Kubernetes
- 使用 kubeadm 创建集群 | Kubernetes
- 高可用拓扑选项 | Kubernetes
- kubeadm init | Kubernetes
- v1.24.2|kubeadm|v1beta3
- Installing kubeadm | Kubernetes
- Ports and Protocols | Kubernetes
- 容器运行时 | Kubernetes
- https://github.com/Mirantis/cri-dockerd
- 配置 cgroup 驱动|Kubernetes
- GitHub: flannel is a network fabric for containers
- 部署和访问 Kubernetes 仪表板(Dashboard) | Kubernetes
2.1.2 - 使用kubespray安装kubernetes
1. 环境准备
1.1. 部署机器
以下机器为虚拟机
| 机器IP | 主机名 | 角色 | 系统版本 | 备注 |
|---|---|---|---|---|
| 172.16.94.140 | kube-master-0 | k8s master | Centos 4.17.14 | 内存:3G |
| 172.16.94.141 | kube-node-41 | k8s node | Centos 4.17.14 | 内存:3G |
| 172.16.94.142 | kube-node-42 | k8s node | Centos 4.17.14 | 内存:3G |
| 172.16.94.135 | 部署管理机 | - |
1.2. 配置管理机
管理机主要用来部署k8s集群,需要安装以下版本的软件,具体可参考:
-
https://github.com/kubernetes-incubator/kubespray#requirements
-
https://github.com/kubernetes-incubator/kubespray/blob/master/requirements.txt
ansible>=2.4.0
jinja2>=2.9.6
netaddr
pbr>=1.6
ansible-modules-hashivault>=3.9.4
hvac
1、安装及配置ansible
- 参考ansible的使用。
- 给部署机器配置SSH的免密登录权限,具体参考ssh免密登录。
2、安装python-netaddr
# 安装pip
yum -y install epel-release
yum -y install python-pip
# 安装python-netaddr
pip install netaddr
3、升级Jinja
# Jinja 2.9 (or newer)
pip install --upgrade jinja2
1.3. 配置部署机器
部署机器即用来运行k8s集群的机器,包括Master和Node。
1、确认系统版本
本文采用centos7的系统,建议将系统内核升级到4.x.x以上。
2、关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
iptables -F
3、关闭swap
Kubespary v2.5.0的版本需要关闭swap,具体参考
- name: Stop if swap enabled
assert:
that: ansible_swaptotal_mb == 0
when: kubelet_fail_swap_on|default(true)
ignore_errors: "{{ ignore_assert_errors }}"
V2.6.0版本去除了swap的检查,具体参考:
执行关闭swap命令swapoff -a。
[root@master ~]#swapoff -a
[root@master ~]#
[root@master ~]# free -m
total used free shared buff/cache available
Mem: 976 366 135 6 474 393
Swap: 0 0 0
# swap 一栏为0,表示已经关闭了swap
4、确认部署机器内存
由于本文采用虚拟机部署,内存可能存在不足的问题,因此将虚拟机内存调整为3G或以上;如果是物理机一般不会有内存不足的问题。具体参考:
- name: Stop if memory is too small for masters
assert:
that: ansible_memtotal_mb >= 1500
ignore_errors: "{{ ignore_assert_errors }}"
when: inventory_hostname in groups['kube-master']
- name: Stop if memory is too small for nodes
assert:
that: ansible_memtotal_mb >= 1024
ignore_errors: "{{ ignore_assert_errors }}"
when: inventory_hostname in groups['kube-node']
1.4. 涉及镜像
Docker版本为17.03.2-ce。
1、Master节点
| 镜像 | 版本 | 大小 | 镜像ID | 备注 |
|---|---|---|---|---|
| gcr.io/google-containers/hyperkube | v1.9.5 | 620 MB | a7e7fdbc5fee | k8s |
| quay.io/coreos/etcd | v3.2.4 | 35.7 MB | 498ffffcfd05 | |
| gcr.io/google_containers/pause-amd64 | 3.0 | 747 kB | 99e59f495ffa | |
| quay.io/calico/node | v2.6.8 | 282 MB | e96a297310fd | calico |
| quay.io/calico/cni | v1.11.4 | 70.8 MB | 4c4cb67d7a88 | calico |
| quay.io/calico/ctl | v1.6.3 | 44.4 MB | 46d3aace8bc6 | calico |
2、Node节点
| 镜像 | 版本 | 大小 | 镜像ID | 备注 |
|---|---|---|---|---|
| gcr.io/google-containers/hyperkube | v1.9.5 | 620 MB | a7e7fdbc5fee | k8s |
| gcr.io/google_containers/pause-amd64 | 3.0 | 747 kB | 99e59f495ffa | |
| quay.io/calico/node | v2.6.8 | 282 MB | e96a297310fd | calico |
| quay.io/calico/cni | v1.11.4 | 70.8 MB | 4c4cb67d7a88 | calico |
| quay.io/calico/ctl | v1.6.3 | 44.4 MB | 46d3aace8bc6 | calico |
| gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64 | 1.14.8 | 40.9 MB | c2ce1ffb51ed | dns |
| gcr.io/google_containers/k8s-dns-sidecar-amd64 | 1.14.8 | 42.2 MB | 6f7f2dc7fab5 | dns |
| gcr.io/google_containers/k8s-dns-kube-dns-amd64 | 1.14.8 | 50.5 MB | 80cc5ea4b547 | dns |
| gcr.io/google_containers/cluster-proportional-autoscaler-amd64 | 1.1.2 | 50.5 MB | 78cf3f492e6b | |
| gcr.io/google_containers/kubernetes-dashboard-amd64 | v1.8.3 | 102 MB | 0c60bcf89900 | dashboard |
| nginx | 1.13 | 109 MB | ae513a47849c | - |
3、说明
- 镜像被墙并且全部镜像下载需要较多时间,建议提前下载到部署机器上。
- hyperkube镜像主要用来运行k8s核心组件(例如kube-apiserver等)。
- 此处使用的网络组件为calico。
2. 部署集群
2.1. 下载kubespary的源码
git clone https://github.com/kubernetes-incubator/kubespray.git
2.2. 编辑配置文件
2.2.1. hosts.ini
hosts.ini主要为部署节点机器信息的文件,路径为:kubespray/inventory/sample/hosts.ini。
cd kubespray
# 复制一份配置进行修改
cp -rfp inventory/sample inventory/k8s
vi inventory/k8s/hosts.ini
例如:
hosts.ini文件可以填写部署机器的登录密码,也可以不填密码而设置ssh的免密登录。
# Configure 'ip' variable to bind kubernetes services on a
# different ip than the default iface
# 主机名 ssh登陆IP ssh用户名 ssh登陆密码 机器IP 子网掩码
kube-master-0 ansible_ssh_host=172.16.94.140 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.140 mask=/24
kube-node-41 ansible_ssh_host=172.16.94.141 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.141 mask=/24
kube-node-42 ansible_ssh_host=172.16.94.142 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.142 mask=/24
# configure a bastion host if your nodes are not directly reachable
# bastion ansible_ssh_host=x.x.x.x
[kube-master]
kube-master-0
[etcd]
kube-master-0
[kube-node]
kube-node-41
kube-node-42
[k8s-cluster:children]
kube-node
kube-master
[calico-rr]
2.2.2. k8s-cluster.yml
k8s-cluster.yml主要为k8s集群的配置文件,路径为:kubespray/inventory/k8s/group_vars/k8s-cluster.yml。该文件可以修改安装的k8s集群的版本,参数为:kube_version: v1.9.5。具体可参考:
2.3. 执行部署操作
涉及文件为cluster.yml。
# 进入主目录
cd kubespray
# 执行部署命令
ansible-playbook -i inventory/k8s/hosts.ini cluster.yml -b -vvv
-vvv 参数表示输出运行日志
如果需要重置可以执行以下命令:
涉及文件为reset.yml。
ansible-playbook -i inventory/k8s/hosts.ini reset.yml -b -vvv
3. 确认部署结果
3.1. ansible的部署结果
ansible命令执行完,出现以下日志,则说明部署成功,否则根据报错内容进行修改。
PLAY RECAP *****************************************************************************
kube-master-0 : ok=309 changed=30 unreachable=0 failed=0
kube-node-41 : ok=203 changed=8 unreachable=0 failed=0
kube-node-42 : ok=203 changed=8 unreachable=0 failed=0
localhost : ok=2 changed=0 unreachable=0 failed=0
以下为部分部署执行日志:
kubernetes/preinstall : Update package management cache (YUM) --------------------23.96s
/root/gopath/src/kubespray/roles/kubernetes/preinstall/tasks/main.yml:121
kubernetes/master : Master | wait for the apiserver to be running ----------------23.44s
/root/gopath/src/kubespray/roles/kubernetes/master/handlers/main.yml:79
kubernetes/preinstall : Install packages requirements ----------------------------20.20s
/root/gopath/src/kubespray/roles/kubernetes/preinstall/tasks/main.yml:203
kubernetes/secrets : Check certs | check if a cert already exists on node --------13.94s
/root/gopath/src/kubespray/roles/kubernetes/secrets/tasks/check-certs.yml:17
gather facts from all instances --------------------------------------------------9.98s
/root/gopath/src/kubespray/cluster.yml:25
kubernetes/node : install | Compare host kubelet with hyperkube container --------9.66s
/root/gopath/src/kubespray/roles/kubernetes/node/tasks/install_host.yml:2
kubernetes-apps/ansible : Kubernetes Apps | Start Resources -----------------------9.27s
/root/gopath/src/kubespray/roles/kubernetes-apps/ansible/tasks/main.yml:37
kubernetes-apps/ansible : Kubernetes Apps | Lay Down KubeDNS Template ------------8.47s
/root/gopath/src/kubespray/roles/kubernetes-apps/ansible/tasks/kubedns.yml:3
download : Sync container ---------------------------------------------------------8.23s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:15
kubernetes-apps/network_plugin/calico : Start Calico resources --------------------7.82s
/root/gopath/src/kubespray/roles/kubernetes-apps/network_plugin/calico/tasks/main.yml:2
download : Download items ---------------------------------------------------------7.67s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:6
download : Download items ---------------------------------------------------------7.48s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:6
download : Sync container ---------------------------------------------------------7.35s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:15
download : Download items ---------------------------------------------------------7.16s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:6
network_plugin/calico : Calico | Copy cni plugins from calico/cni container -------7.10s
/root/gopath/src/kubespray/roles/network_plugin/calico/tasks/main.yml:62
download : Download items ---------------------------------------------------------7.04s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:6
download : Download items ---------------------------------------------------------7.01s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:6
download : Sync container ---------------------------------------------------------7.00s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:15
download : Download items ---------------------------------------------------------6.98s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:6
download : Download items ---------------------------------------------------------6.79s
/root/gopath/src/kubespray/roles/download/tasks/main.yml:6
3.2. k8s集群运行结果
1、k8s组件信息
# kubectl get all --namespace=kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
ds/calico-node 3 3 3 3 3 <none> 2h
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/kube-dns 2 2 2 2 2h
deploy/kubedns-autoscaler 1 1 1 1 2h
deploy/kubernetes-dashboard 1 1 1 1 2h
NAME DESIRED CURRENT READY AGE
rs/kube-dns-79d99cdcd5 2 2 2 2h
rs/kubedns-autoscaler-5564b5585f 1 1 1 2h
rs/kubernetes-dashboard-69cb58d748 1 1 1 2h
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
ds/calico-node 3 3 3 3 3 <none> 2h
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/kube-dns 2 2 2 2 2h
deploy/kubedns-autoscaler 1 1 1 1 2h
deploy/kubernetes-dashboard 1 1 1 1 2h
NAME DESIRED CURRENT READY AGE
rs/kube-dns-79d99cdcd5 2 2 2 2h
rs/kubedns-autoscaler-5564b5585f 1 1 1 2h
rs/kubernetes-dashboard-69cb58d748 1 1 1 2h
NAME READY STATUS RESTARTS AGE
po/calico-node-22vsg 1/1 Running 0 2h
po/calico-node-t7zgw 1/1 Running 0 2h
po/calico-node-zqnx8 1/1 Running 0 2h
po/kube-apiserver-kube-master-0 1/1 Running 0 22h
po/kube-controller-manager-kube-master-0 1/1 Running 0 2h
po/kube-dns-79d99cdcd5-f2t6t 3/3 Running 0 2h
po/kube-dns-79d99cdcd5-gw944 3/3 Running 0 2h
po/kube-proxy-kube-master-0 1/1 Running 2 22h
po/kube-proxy-kube-node-41 1/1 Running 3 22h
po/kube-proxy-kube-node-42 1/1 Running 3 22h
po/kube-scheduler-kube-master-0 1/1 Running 0 2h
po/kubedns-autoscaler-5564b5585f-lt9bb 1/1 Running 0 2h
po/kubernetes-dashboard-69cb58d748-wmb9x 1/1 Running 0 2h
po/nginx-proxy-kube-node-41 1/1 Running 3 22h
po/nginx-proxy-kube-node-42 1/1 Running 3 22h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/kube-dns ClusterIP 10.233.0.3 <none> 53/UDP,53/TCP 2h
svc/kubernetes-dashboard ClusterIP 10.233.27.24 <none> 443/TCP 2h
2、k8s节点信息
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
kube-master-0 Ready master 22h v1.9.5
kube-node-41 Ready node 22h v1.9.5
kube-node-42 Ready node 22h v1.9.5
3、组件健康信息
# kubectl get cs
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {"health": "true"}
4. k8s集群扩容节点
4.1. 修改hosts.ini文件
如果需要扩容Node节点,则修改hosts.ini文件,增加新增的机器信息。例如,要增加节点机器kube-node-43(IP为172.16.94.143),修改后的文件内容如下:
# Configure 'ip' variable to bind kubernetes services on a
# different ip than the default iface
# 主机名 ssh登陆IP ssh用户名 ssh登陆密码 机器IP 子网掩码
kube-master-0 ansible_ssh_host=172.16.94.140 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.140 mask=/24
kube-node-41 ansible_ssh_host=172.16.94.141 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.141 mask=/24
kube-node-42 ansible_ssh_host=172.16.94.142 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.142 mask=/24
kube-node-43 ansible_ssh_host=172.16.94.143 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.143 mask=/24
# configure a bastion host if your nodes are not directly reachable
# bastion ansible_ssh_host=x.x.x.x
[kube-master]
kube-master-0
[etcd]
kube-master-0
[kube-node]
kube-node-41
kube-node-42
kube-node-43
[k8s-cluster:children]
kube-node
kube-master
[calico-rr]
4.2. 执行扩容命令
涉及文件为scale.yml。
# 进入主目录
cd kubespray
# 执行部署命令
ansible-playbook -i inventory/k8s/hosts.ini scale.yml -b -vvv
4.3. 检查扩容结果
1、ansible的执行结果
PLAY RECAP ***************************************
kube-node-41 : ok=228 changed=11 unreachable=0 failed=0
kube-node-42 : ok=197 changed=6 unreachable=0 failed=0
kube-node-43 : ok=227 changed=69 unreachable=0 failed=0 # 新增Node节点
localhost : ok=2 changed=0 unreachable=0 failed=0
2、k8s的节点信息
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
kube-master-0 Ready master 1d v1.9.5
kube-node-41 Ready node 1d v1.9.5
kube-node-42 Ready node 1d v1.9.5
kube-node-43 Ready node 1m v1.9.5 #该节点为新增Node节点
可以看到新增的kube-node-43节点已经扩容完成。
3、k8s组件信息
# kubectl get po --namespace=kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE
calico-node-22vsg 1/1 Running 0 10h 172.16.94.140 kube-master-0
calico-node-8fz9x 1/1 Running 2 27m 172.16.94.143 kube-node-43
calico-node-t7zgw 1/1 Running 0 10h 172.16.94.142 kube-node-42
calico-node-zqnx8 1/1 Running 0 10h 172.16.94.141 kube-node-41
kube-apiserver-kube-master-0 1/1 Running 0 1d 172.16.94.140 kube-master-0
kube-controller-manager-kube-master-0 1/1 Running 0 10h 172.16.94.140 kube-master-0
kube-dns-79d99cdcd5-f2t6t 3/3 Running 0 10h 10.233.100.194 kube-node-41
kube-dns-79d99cdcd5-gw944 3/3 Running 0 10h 10.233.107.1 kube-node-42
kube-proxy-kube-master-0 1/1 Running 2 1d 172.16.94.140 kube-master-0
kube-proxy-kube-node-41 1/1 Running 3 1d 172.16.94.141 kube-node-41
kube-proxy-kube-node-42 1/1 Running 3 1d 172.16.94.142 kube-node-42
kube-proxy-kube-node-43 1/1 Running 0 26m 172.16.94.143 kube-node-43
kube-scheduler-kube-master-0 1/1 Running 0 10h 172.16.94.140 kube-master-0
kubedns-autoscaler-5564b5585f-lt9bb 1/1 Running 0 10h 10.233.100.193 kube-node-41
kubernetes-dashboard-69cb58d748-wmb9x 1/1 Running 0 10h 10.233.107.2 kube-node-42
nginx-proxy-kube-node-41 1/1 Running 3 1d 172.16.94.141 kube-node-41
nginx-proxy-kube-node-42 1/1 Running 3 1d 172.16.94.142 kube-node-42
nginx-proxy-kube-node-43 1/1 Running 0 26m 172.16.94.143 kube-node-43
5. 部署高可用集群
将hosts.ini文件中的master和etcd的机器增加到多台,执行部署命令。
ansible-playbook -i inventory/k8s/hosts.ini cluster.yml -b -vvv
例如:
# Configure 'ip' variable to bind kubernetes services on a
# different ip than the default iface
# 主机名 ssh登陆IP ssh用户名 ssh登陆密码 机器IP 子网掩码
kube-master-0 ansible_ssh_host=172.16.94.140 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.140 mask=/24
kube-master-1 ansible_ssh_host=172.16.94.144 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.144 mask=/24
kube-master-2 ansible_ssh_host=172.16.94.145 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.145 mask=/24
kube-node-41 ansible_ssh_host=172.16.94.141 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.141 mask=/24
kube-node-42 ansible_ssh_host=172.16.94.142 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.142 mask=/24
kube-node-43 ansible_ssh_host=172.16.94.143 ansible_ssh_user=root ansible_ssh_pass=123 ip=172.16.94.143 mask=/24
# configure a bastion host if your nodes are not directly reachable
# bastion ansible_ssh_host=x.x.x.x
[kube-master]
kube-master-0
kube-master-1
kube-master-2
[etcd]
kube-master-0
kube-master-1
kube-master-2
[kube-node]
kube-node-41
kube-node-42
kube-node-43
[k8s-cluster:children]
kube-node
kube-master
[calico-rr]
6. 升级k8s集群
选择对应的k8s版本信息,执行升级命令。涉及文件为upgrade-cluster.yml。
ansible-playbook upgrade-cluster.yml -b -i inventory/k8s/hosts.ini -e kube_version=v1.10.4 -vvv
7. troubles shooting
在使用kubespary部署k8s集群时,主要遇到以下报错。
7.1. python-netaddr未安装
- 报错内容:
fatal: [node1]: FAILED! => {"failed": true, "msg": "The ipaddr filter requires python-netaddr be installed on the ansible controller"}
- 解决方法:
需要安装 python-netaddr,具体参考上述[环境准备]内容。
7.2. swap未关闭
- 报错内容:
fatal: [kube-master-0]: FAILED! => {
"assertion": "ansible_swaptotal_mb == 0",
"changed": false,
"evaluated_to": false
}
fatal: [kube-node-41]: FAILED! => {
"assertion": "ansible_swaptotal_mb == 0",
"changed": false,
"evaluated_to": false
}
fatal: [kube-node-42]: FAILED! => {
"assertion": "ansible_swaptotal_mb == 0",
"changed": false,
"evaluated_to": false
}
- 解决方法:
所有部署机器执行swapoff -a关闭swap,具体参考上述[环境准备]内容。
7.3. 部署机器内存过小
- 报错内容:
TASK [kubernetes/preinstall : Stop if memory is too small for masters] *********************************************************************************************************************************************************************************************************
task path: /root/gopath/src/kubespray/roles/kubernetes/preinstall/tasks/verify-settings.yml:52
Friday 10 August 2018 21:50:26 +0800 (0:00:00.940) 0:01:14.088 *********
fatal: [kube-master-0]: FAILED! => {
"assertion": "ansible_memtotal_mb >= 1500",
"changed": false,
"evaluated_to": false
}
TASK [kubernetes/preinstall : Stop if memory is too small for nodes] ***********************************************************************************************************************************************************************************************************
task path: /root/gopath/src/kubespray/roles/kubernetes/preinstall/tasks/verify-settings.yml:58
Friday 10 August 2018 21:50:27 +0800 (0:00:00.570) 0:01:14.659 *********
fatal: [kube-node-41]: FAILED! => {
"assertion": "ansible_memtotal_mb >= 1024",
"changed": false,
"evaluated_to": false
}
fatal: [kube-node-42]: FAILED! => {
"assertion": "ansible_memtotal_mb >= 1024",
"changed": false,
"evaluated_to": false
}
to retry, use: --limit @/root/gopath/src/kubespray/cluster.retry
- 解决方法:
调大所有部署机器的内存,本示例中调整为3G或以上。
7.4. kube-scheduler组件运行失败
kube-scheduler组件运行失败,导致http://localhost:10251/healthz调用失败。
- 报错内容:
FAILED - RETRYING: Master | wait for kube-scheduler (1 retries left).
FAILED - RETRYING: Master | wait for kube-scheduler (1 retries left).
fatal: [node1]: FAILED! => {"attempts": 60, "changed": false, "content": "", "failed": true, "msg": "Status code was not [200]: Request failed: <urlopen error [Errno 111] Connection refused>", "redirected": false, "status": -1, "url": "http://localhost:10251/healthz"}
- 解决方法:
可能是内存不足导致,本示例中调大了部署机器的内存。
7.5. docker安装包冲突
- 报错内容:
failed: [k8s-node-1] (item={u'name': u'docker-engine-1.13.1-1.el7.centos'}) => {
"attempts": 4,
"changed": false,
...
"item": {
"name": "docker-engine-1.13.1-1.el7.centos"
},
"msg": "Error: docker-ce-selinux conflicts with 2:container-selinux-2.66-1.el7.noarch\n",
"rc": 1,
"results": [
"Loaded plugins: fastestmirror\nLoading mirror speeds from cached hostfile\n * elrepo: mirrors.tuna.tsinghua.edu.cn\n * epel: mirrors.tongji.edu.cn\nPackage docker-engine is obsoleted by docker-ce, trying to install docker-ce-17.03.2.ce-1.el7.centos.x86_64 instead\nResolving Dependencies\n--> Running transaction check\n---> Package docker-ce.x86_64 0:17.03.2.ce-1.el7.centos will be installed\n--> Processing Dependency: docker-ce-selinux >= 17.03.2.ce-1.el7.centos for package: docker-ce-17.03.2.ce-1.el7.centos.x86_64\n--> Processing Dependency: libltdl.so.7()(64bit) for package: docker-ce-17.03.2.ce-1.el7.centos.x86_64\n--> Running transaction check\n---> Package docker-ce-selinux.noarch 0:17.03.2.ce-1.el7.centos will be installed\n---> Package libtool-ltdl.x86_64 0:2.4.2-22.el7_3 will be installed\n--> Processing Conflict: docker-ce-selinux-17.03.2.ce-1.el7.centos.noarch conflicts docker-selinux\n--> Restarting Dependency Resolution with new changes.\n--> Running transaction check\n---> Package container-selinux.noarch 2:2.55-1.el7 will be updated\n---> Package container-selinux.noarch 2:2.66-1.el7 will be an update\n--> Processing Conflict: docker-ce-selinux-17.03.2.ce-1.el7.centos.noarch conflicts docker-selinux\n--> Finished Dependency Resolution\n You could try using --skip-broken to work around the problem\n You could try running: rpm -Va --nofiles --nodigest\n"
]
}
- 解决方法:
卸载旧的docker版本,由kubespary自动安装。
sudo yum remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
参考文章:
2.1.3 - 使用minikube安装kubernetes
以下内容基于Linux系统,特别为Ubuntu系统
1. 安装kubectl
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
下载指定版本,例如下载v1.9.0版本
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
2. 安装minikube
minikube的源码地址:https://github.com/kubernetes/minikube
2.1 安装minikube
以下命令为安装latest版本的minikube。
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
安装指定版本可到https://github.com/kubernetes/minikube/releases下载对应版本。
例如:以下为安装v0.28.2版本
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.28.2/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
2.2 minikube命令帮助
Minikube is a CLI tool that provisions and manages single-node Kubernetes clusters optimized for development workflows.
Usage:
minikube [command]
Available Commands:
addons Modify minikube's kubernetes addons
cache Add or delete an image from the local cache.
completion Outputs minikube shell completion for the given shell (bash or zsh)
config Modify minikube config
dashboard Opens/displays the kubernetes dashboard URL for your local cluster
delete Deletes a local kubernetes cluster
docker-env Sets up docker env variables; similar to '$(docker-machine env)'
get-k8s-versions Gets the list of Kubernetes versions available for minikube when using the localkube bootstrapper
ip Retrieves the IP address of the running cluster
logs Gets the logs of the running localkube instance, used for debugging minikube, not user code
mount Mounts the specified directory into minikube
profile Profile sets the current minikube profile
service Gets the kubernetes URL(s) for the specified service in your local cluster
ssh Log into or run a command on a machine with SSH; similar to 'docker-machine ssh'
ssh-key Retrieve the ssh identity key path of the specified cluster
start Starts a local kubernetes cluster
status Gets the status of a local kubernetes cluster
stop Stops a running local kubernetes cluster
update-check Print current and latest version number
update-context Verify the IP address of the running cluster in kubeconfig.
version Print the version of minikube
Flags:
--alsologtostderr log to standard error as well as files
-b, --bootstrapper string The name of the cluster bootstrapper that will set up the kubernetes cluster. (default "localkube")
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--loglevel int Log level (0 = DEBUG, 5 = FATAL) (default 1)
--logtostderr log to standard error instead of files
-p, --profile string The name of the minikube VM being used.
This can be modified to allow for multiple minikube instances to be run independently (default "minikube")
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level log level for V logs
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
Use "minikube [command] --help" for more information about a command.
3. 使用minikube安装k8s集群
3.1. minikube start
可以以Docker的方式运行k8s的组件,但需要先安装Docker(可参考Docker安装),启动参数使用--vm-driver=none。
minikube start --vm-driver=none
例如:
root@ubuntu:~# minikube start --vm-driver=none
Starting local Kubernetes v1.10.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Downloading kubeadm v1.10.0
Downloading kubelet v1.10.0
^[[DFinished Downloading kubelet v1.10.0
Finished Downloading kubeadm v1.10.0
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
===================
WARNING: IT IS RECOMMENDED NOT TO RUN THE NONE DRIVER ON PERSONAL WORKSTATIONS
The 'none' driver will run an insecure kubernetes apiserver as root that may leave the host vulnerable to CSRF attacks
When using the none driver, the kubectl config and credentials generated will be root owned and will appear in the root home directory.
You will need to move the files to the appropriate location and then set the correct permissions. An example of this is below:
sudo mv /root/.kube $HOME/.kube # this will write over any previous configuration
sudo chown -R $USER $HOME/.kube
sudo chgrp -R $USER $HOME/.kube
sudo mv /root/.minikube $HOME/.minikube # this will write over any previous configuration
sudo chown -R $USER $HOME/.minikube
sudo chgrp -R $USER $HOME/.minikube
This can also be done automatically by setting the env var CHANGE_MINIKUBE_NONE_USER=true
Loading cached images from config file.
安装指定版本的kubernetes集群
# 查阅版本
minikube get-k8s-versions
# 选择版本启动
minikube start --kubernetes-version v1.7.3 --vm-driver=none
3.2. minikube status
$ minikube status
minikube: Running
cluster: Running
kubectl: Correctly Configured: pointing to minikube-vm at 172.16.94.139
3.3. minikube stop
minikube stop 命令可以用来停止集群。 该命令会关闭 minikube 虚拟机,但将保留所有集群状态和数据。 再次启动集群将恢复到之前的状态。
3.4. minikube delete
minikube delete 命令可以用来删除集群。 该命令将关闭并删除 minikube 虚拟机。没有数据或状态会被保存下来。
4. 查看部署结果
4.1. 部署组件
root@ubuntu:~# kubectl get all --namespace=kube-system
NAME READY STATUS RESTARTS AGE
pod/etcd-minikube 1/1 Running 0 38m
pod/kube-addon-manager-minikube 1/1 Running 0 38m
pod/kube-apiserver-minikube 1/1 Running 1 39m
pod/kube-controller-manager-minikube 1/1 Running 0 38m
pod/kube-dns-86f4d74b45-bdfnx 3/3 Running 0 38m
pod/kube-proxy-dqdvg 1/1 Running 0 38m
pod/kube-scheduler-minikube 1/1 Running 0 38m
pod/kubernetes-dashboard-5498ccf677-c2gnh 1/1 Running 0 38m
pod/storage-provisioner 1/1 Running 0 38m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 38m
service/kubernetes-dashboard NodePort 10.104.48.227 <none> 80:30000/TCP 38m
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/kube-proxy 1 1 1 1 1 <none> 38m
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/kube-dns 1 1 1 1 38m
deployment.apps/kubernetes-dashboard 1 1 1 1 38m
NAME DESIRED CURRENT READY AGE
replicaset.apps/kube-dns-86f4d74b45 1 1 1 38m
replicaset.apps/kubernetes-dashboard-5498ccf677 1 1 1 38m
4.2. dashboard
通过访问ip:port,例如:http://172.16.94.139:30000/,可以访问k8s的dashboard控制台。
<img src="http://res.cloudinary.com/dqxtn0ick/image/upload/v1533695750/article/kubernetes/arch/dashboard.png" width = "100%"/>
5. troubleshooting
5.1. 没有安装VirtualBox
[root@minikube ~]# minikube start
Starting local Kubernetes v1.10.0 cluster...
Starting VM...
Downloading Minikube ISO
160.27 MB / 160.27 MB [============================================] 100.00% 0s
E0727 15:47:08.655647 9407 start.go:174] Error starting host: Error creating host: Error executing step: Running precreate checks.
: VBoxManage not found. Make sure VirtualBox is installed and VBoxManage is in the path.
Retrying.
E0727 15:47:08.656994 9407 start.go:180] Error starting host: Error creating host: Error executing step: Running precreate checks.
: VBoxManage not found. Make sure VirtualBox is installed and VBoxManage is in the path
================================================================================
An error has occurred. Would you like to opt in to sending anonymized crash
information to minikube to help prevent future errors?
To opt out of these messages, run the command:
minikube config set WantReportErrorPrompt false
================================================================================
Please enter your response [Y/n]:
解决方法,先安装VirtualBox。
5.2. 没有安装Docker
[root@minikube ~]# minikube start --vm-driver=none
Starting local Kubernetes v1.10.0 cluster...
Starting VM...
E0727 15:56:54.936706 9441 start.go:174] Error starting host: Error creating host: Error executing step: Running precreate checks.
: docker cannot be found on the path for this machine. A docker installation is a requirement for using the none driver: exec: "docker": executable file not found in $PATH.
Retrying.
E0727 15:56:54.938930 9441 start.go:180] Error starting host: Error creating host: Error executing step: Running precreate checks.
: docker cannot be found on the path for this machine. A docker installation is a requirement for using the none driver: exec: "docker": executable file not found in $PATH
解决方法,先安装Docker。
文章参考:
https://github.com/kubernetes/minikube
https://kubernetes.io/docs/setup/minikube/
2.1.4 - 使用kind安装kubernetes
1. 安装kind
On mac or linux
curl -Lo ./kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(uname)-amd64"
chmod +x ./kind
mv ./kind /some-dir-in-your-PATH/kind
2. 创建k8s集群
$ kind create cluster
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.17.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
查看集群信息
$ kubectl cluster-info --context kind-kind
Kubernetes master is running at https://127.0.0.1:32768
KubeDNS is running at https://127.0.0.1:32768/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
查看node
$ kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
kind-control-plane Ready master 35h v1.17.0 172.17.0.2 <none> Ubuntu 19.10 3.10.107-1-tlinux2_kvm_guest-0049 containerd://1.3.2
查看pod
$ kubectl get po --all-namespaces -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-6955765f44-lqk9v 1/1 Running 0 35h 10.244.0.4 kind-control-plane <none> <none>
kube-system coredns-6955765f44-zpsmc 1/1 Running 0 35h 10.244.0.3 kind-control-plane <none> <none>
kube-system etcd-kind-control-plane 1/1 Running 0 35h 172.17.0.2 kind-control-plane <none> <none>
kube-system kindnet-8mt7d 1/1 Running 0 35h 172.17.0.2 kind-control-plane <none> <none>
kube-system kube-apiserver-kind-control-plane 1/1 Running 0 35h 172.17.0.2 kind-control-plane <none> <none>
kube-system kube-controller-manager-kind-control-plane 1/1 Running 0 35h 172.17.0.2 kind-control-plane <none> <none>
kube-system kube-proxy-5w25s 1/1 Running 0 35h 172.17.0.2 kind-control-plane <none> <none>
kube-system kube-scheduler-kind-control-plane 1/1 Running 0 35h 172.17.0.2 kind-control-plane <none> <none>
local-path-storage local-path-provisioner-7745554f7f-dckzr 1/1 Running 0 35h 10.244.0.2 kind-control-plane <none> <none>
docker ps
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
93b291f99dd4 kindest/node:v1.17.0 "/usr/local/bin/entr…" 2 minutes ago Up 2 minutes 127.0.0.1:32768->6443/tcp kind-control-plane
3. kindest/node容器内进程
$ docker exec -it 93b291f99dd4 bash
root@kind-control-plane:/# ps auxw
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.0 19512 7480 ? Ss 03:18 0:00 /sbin/init
root 105 0.0 0.0 26396 7344 ? S<s 03:18 0:00 /lib/systemd/systemd-journald
root 141 2.3 0.3 2374736 51564 ? Ssl 03:18 0:06 /usr/local/bin/containerd
root 325 0.0 0.0 112540 5036 ? Sl 03:18 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 3f415d609e15ef12b9f53557891c311c156b912d5a326544a25c8b29cfa9d366 -address /run/containerd/containerd.sock
root 346 0.0 0.0 1012 4 ? Ss 03:18 0:00 /pause
root 370 0.0 0.0 112540 5108 ? Sl 03:18 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 1e1f3eed09f701fb621325e7b9e96d1c3de60ebd3bd64e0aec376e9490cf0e57 -address /run/containerd/containerd.sock
root 397 0.0 0.0 112540 4684 ? Sl 03:18 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id c5e451089a1a5b3dfb2cc68ee27ac7d414285be55ecfdc5bd59180fbfbc7df2e -address /run/containerd/containerd.sock
root 424 0.0 0.0 112540 4924 ? Sl 03:18 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 81e35f29ac8c2dda344125a10e3791be7ccf788a88f1efbc3397fa319f02881f -address /run/containerd/containerd.sock
root 443 0.0 0.0 1012 4 ? Ss 03:18 0:00 /pause
root 458 0.0 0.0 1012 4 ? Ss 03:18 0:00 /pause
root 465 0.0 0.0 1012 4 ? Ss 03:18 0:00 /pause
root 548 0.7 0.1 145500 27724 ? Ssl 03:18 0:02 kube-scheduler --authentication-kubeconfig=/etc/kubernetes/scheduler.conf --authorization-kubeconfig=/etc/kubernetes/scheduler.conf --bind-address=127.0.0.1 --kubeconfig=/etc/kubernetes/scheduler.conf --leader-elect=true
root 589 1.0 0.3 159536 54384 ? Ssl 03:18 0:02 kube-controller-manager --allocate-node-cidrs=true --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf --bind-address=127.0.0.1 --client-ca-file=/etc/kubernetes/pki/ca.cr
root 613 3.8 1.6 445780 273484 ? Ssl 03:18 0:10 kube-apiserver --advertise-address=172.17.0.2 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --enable-bootstrap-token-auth=true --etcd-cafile=/etc/kubernetes/
root 660 1.4 0.2 10613604 37448 ? Ssl 03:18 0:04 etcd --advertise-client-urls=https://172.17.0.2:2379 --cert-file=/etc/kubernetes/pki/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/etcd --initial-advertise-peer-urls=https://172.17.0.2:2380 --initial-cluster=kind-control-plane=https://172.
root 718 1.3 0.3 2084848 52772 ? Ssl 03:18 0:03 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --container-runtime-endpoint=/run/containerd/containerd.sock --fail
root 876 0.0 0.0 112540 5084 ? Sl 03:18 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id adfbea8fec5ac6986407291f5bfc5aecead176954e5dabbe1517b98dd77bf78b -address /run/containerd/containerd.sock
root 893 0.0 0.0 112540 4796 ? Sl 03:18 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 53bdce023626b60ffaa0548b5888e457dc9c3bc45c7808a385dd0f63dcc90327 -address /run/containerd/containerd.sock
root 924 0.0 0.0 1012 4 ? Ss 03:18 0:00 /pause
root 931 0.0 0.0 1012 4 ? Ss 03:18 0:00 /pause
root 1000 0.0 0.0 127616 11100 ? Ssl 03:18 0:00 /bin/kindnetd
root 1017 0.0 0.1 141060 19420 ? Ssl 03:18 0:00 /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=kind-control-plane
root 1066 0.0 0.0 0 0 ? Z 03:18 0:00 [iptables-nft-sa] <defunct>
root 1080 0.0 0.0 0 0 ? Z 03:18 0:00 [iptables-nft-sa] <defunct>
root 1241 0.0 0.0 112540 5156 ? Sl 03:19 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 5cbd7bbe186cf5847786c7a03aa4c6f82e6c805d0a189f0f3e8fb1750594260d -address /run/containerd/containerd.sock
root 1262 0.0 0.0 1012 4 ? Ss 03:19 0:00 /pause
root 1303 0.1 0.0 134372 14088 ? Ssl 03:19 0:00 local-path-provisioner --debug start --helper-image k8s.gcr.io/debian-base:v2.0.0 --config /etc/config/config.json
root 1411 0.0 0.0 112540 4876 ? Sl 03:19 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 196b440345cb5ef47a6c31222323d35bbfef85d1d79c149ec0e3a6e22022a5f0 -address /run/containerd/containerd.sock
root 1437 0.0 0.0 1012 4 ? Ss 03:19 0:00 /pause
root 1450 0.0 0.0 112540 4380 ? Sl 03:19 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id de7bdf052083978c78708383f842567d4fb38adff22a56792437a4de82425afe -address /run/containerd/containerd.sock
root 1480 0.0 0.0 1012 4 ? Ss 03:19 0:00 /pause
root 1530 0.1 0.1 144324 19056 ? Ssl 03:19 0:00 /coredns -conf /etc/coredns/Corefile
root 1531 0.1 0.1 144580 19204 ? Ssl 03:19 0:00 /coredns -conf /etc/coredns/Corefile
参考:
2.1.5 - 安装k8s dashboard
1. 部署dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
镜像: kubernetesui/dashboard:v2.5.0
默认端口:8443
登录页面需要填入token或kubeconfig

2. 登录dashboard
2.1. 创建超级管理员
参考:dashboard/creating-sample-user
创建dashboard-adminuser.yaml文件如下:
k8s 1.24+版本需要自行创建secret绑定serviceaccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: v1
kind: Secret
metadata:
name: admin-user-secret
namespace: kubernetes-dashboard
annotations:
kubernetes.io/service-account.name: "admin-user"
type: kubernetes.io/service-account-token
创建serviceaccount和ClusterRoleBinding,绑定cluster-admin的超级管理员的权限。
kubectl apply -f dashboard-adminuser.yaml
创建用户token
kubectl -n kubernetes-dashboard create token admin-user --duration 8760h
或者通过secret查询token
kubectl get secret admin-user-secret -n kubernetes-dashboard -o jsonpath={".data.token"} | base64 -d
移除账号
kubectl -n kubernetes-dashboard delete serviceaccount admin-user
kubectl -n kubernetes-dashboard delete clusterrolebinding admin-user
2.2. 创建Namespace管理员
1、创建角色权限(role)
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: <namespace>
name: <namespace>-admin-role
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
2、创建用户账号(ServiceAccount)
apiVersion: v1
kind: ServiceAccount
metadata:
name: <namespace>-admin-user
namespace: <namespace>
创建secret 可自动生成token
apiVersion: v1
kind: Secret
metadata:
name: ${SecretName}
namespace: ${ServiceAccountNS}
annotations:
kubernetes.io/service-account.name: "${ServiceAccountName}"
type: kubernetes.io/service-account-token
3、创建角色绑定关系
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: <namespace>-admin-user
namespace: <namespace>
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: <namespace>-admin-role
subjects:
- kind: ServiceAccount
name: <namespace>-admin-user
namespace: <namespace>
4、生成token
kubectl -n <namespace> create token <ServiceAccount> --duration 8760h
或者通过上述secret中的token获得
kubectl get secret ${SecretName} -n ${ServiceAccountNS} -o jsonpath={".data.token"} | base64 -d
2.3. 创建只读账户
集群默认提供了几种命名空间级别的权限,分别设置ClusterRole: [admin, edit, view], 将授权设置为ClusterRole为view即可。
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: <namespace>-admin-user
namespace: <namespace>
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: <namespace>-admin-user
namespace: <namespace>
3. 集成SSO登录
社区提供了添加Authorization header的方式来集成自定义的SSO登录。即在HTTP请求中增加Header: Authorization: Bearer <token>。该操作可以通过apisix或Nginx等插件注入Header。
参考:
2.2 - kubeadm升级k8s集群
本文主要说明如何使用kubeadm来升级k8s集群。
1. 版本注意事项
假设k8s的版本格式为x.y.z,那么使用kubeadm最多只能升级到y+1版本,或者是当前y版本的最新版本。例如你k8s集群的版本为1.24.x,那么你最大版本只能下载1.25.x的kubeadm来升级版本。
因此升级前需要执行以下命令来验证可升级的版本。
kubeadm upgrade plan
1.1. 版本跨度过大
如果出现以下报错,说明升级的版本跨度过大。
# kubeadm的版本为v1.26.7
./kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"26", GitVersion:"v1.26.7", GitCommit:"84e1fc493a47446df2e155e70fca768d2653a398", GitTreeState:"clean", BuildDate:"2023-07-19T12:22:13Z", GoVersion:"go1.20.6", Compiler:"gc", Platform:"linux/amd64"}
# 当前k8s集群版本1.24.2 版本跨度过大。
./kubeadm upgrade plan
[upgrade/config] Making sure the configuration is correct:
[upgrade/config] Reading configuration from the cluster...
[upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[upgrade/config] FATAL: this version of kubeadm only supports deploying clusters with the control plane version >= 1.25.0. Current version: v1.24.2
To see the stack trace of this error execute with --v=5 or higher
1.2. 可升级的版本计划
可升级的版本计划如下
当前的k8s版本为1.24.2,可升级的版本是1.24.16或1.25.12,其中etcd升级的版本为3.5.6-0。
./kubeadm upgrade plan
[upgrade/config] Making sure the configuration is correct:
[upgrade/config] Reading configuration from the cluster...
[upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[preflight] Running pre-flight checks.
[upgrade] Running cluster health checks
[upgrade] Fetching available versions to upgrade to
[upgrade/versions] Cluster version: v1.24.2
[upgrade/versions] kubeadm version: v1.25.12
I0815 21:41:34.096199 2934255 version.go:256] remote version is much newer: v1.27.4; falling back to: stable-1.25
[upgrade/versions] Target version: v1.25.12
[upgrade/versions] Latest version in the v1.24 series: v1.24.16
Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT TARGET
kubelet 4 x v1.24.2 v1.24.16
Upgrade to the latest version in the v1.24 series:
COMPONENT CURRENT TARGET
kube-apiserver v1.24.2 v1.24.16
kube-controller-manager v1.24.2 v1.24.16
kube-scheduler v1.24.2 v1.24.16
kube-proxy v1.24.2 v1.24.16
CoreDNS v1.8.6 v1.9.3
etcd 3.5.3-0 3.5.6-0
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.24.16
_____________________________________________________________________
Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT TARGET
kubelet 4 x v1.24.2 v1.25.12
Upgrade to the latest stable version:
COMPONENT CURRENT TARGET
kube-apiserver v1.24.2 v1.25.12
kube-controller-manager v1.24.2 v1.25.12
kube-scheduler v1.24.2 v1.25.12
kube-proxy v1.24.2 v1.25.12
CoreDNS v1.8.6 v1.9.3
etcd 3.5.3-0 3.5.6-0
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.25.12
_____________________________________________________________________
The table below shows the current state of component configs as understood by this version of kubeadm.
Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or
resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually
upgrade to is denoted in the "PREFERRED VERSION" column.
API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED
kubeproxy.config.k8s.io v1alpha1 v1alpha1 no
kubelet.config.k8s.io v1beta1 v1beta1 no
_____________________________________________________________________
2. 版本升级步骤
2.1. 准备工作
-
下载指定版本的kubeadm二进制
-
查看升级计划:kubeadm upgrade plan
2.2. 升级master节点
2.2.1. 升级第一个master节点
kubeadm upgrade apply v1.25.x -f
升级结束会查看都以下输出:
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.25.x". Enjoy!
[upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.
2.2.2. 手动升级你的 CNI 驱动插件。
你的容器网络接口(CNI)驱动应该提供了程序自身的升级说明。 参阅插件页面查找你的 CNI 驱动, 并查看是否需要其他升级步骤。
如果 CNI 驱动作为 DaemonSet 运行,则在其他控制平面节点上不需要此步骤。
2.2.3. 升级其他master节点
下载指定版本的kubeadm组件。使用以下命令升级,注意区别于第一个master的升级命令。
kubeadm upgrade node
2.2.4. 升级master的kubelet组件
将节点标记为不可调度并驱逐所有负载,准备节点的维护:
# 将 <node-to-drain> 替换为你要腾空的控制面节点名称
kubectl drain <node-to-drain> --ignore-daemonsets
下载kubelet和kubectl
# 用最新的补丁版本替换 1.27.x-00 中的 x
apt-mark unhold kubelet kubectl && \
apt-get update && apt-get install -y kubelet=1.27.x-00 kubectl=1.27.x-00 && \
apt-mark hold kubelet kubectl
重启kubelet
sudo systemctl daemon-reload
sudo systemctl restart kubelet
解除节点保护
# 将 <node-to-uncordon> 替换为你的节点名称
kubectl uncordon <node-to-uncordon>
2.3. 升级worker节点
同 2.2.4. 升级master的kubelet组件的步骤,worker节点只需要升级kubelet。
3. 升级版本回滚
kubeadm升级过程中会把相关目录备份到/etc/kubernetes/tmp目录,备份内容如下:
tmp/
├── kubeadm-backup-etcd-2023-08-16-14-50-50
│ └── etcd
└── kubeadm-backup-manifests-2023-08-16-14-50-50
├── etcd.yaml
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
└── kube-scheduler.yaml
如果 kubeadm upgrade 失败并且没有回滚,例如由于执行期间节点意外关闭, 你可以再次运行 kubeadm upgrade。 此命令是幂等的,并最终确保实际状态是你声明的期望状态。
要从故障状态恢复,你还可以运行 kubeadm upgrade apply --force 而无需更改集群正在运行的版本。
在升级期间,kubeadm 向 /etc/kubernetes/tmp 目录下的如下备份文件夹写入数据:
kubeadm-backup-etcd-<date>-<time>kubeadm-backup-manifests-<date>-<time>
kubeadm-backup-etcd 包含当前控制面节点本地 etcd 成员数据的备份。 如果 etcd 升级失败并且自动回滚也无法修复,则可以将此文件夹中的内容复制到 /var/lib/etcd 进行手工修复。如果使用的是外部的 etcd,则此备份文件夹为空。
kubeadm-backup-manifests 包含当前控制面节点的静态 Pod 清单文件的备份版本。 如果升级失败并且无法自动回滚,则此文件夹中的内容可以复制到 /etc/kubernetes/manifests 目录实现手工恢复。 如果由于某些原因,在升级前后某个组件的清单未发生变化,则 kubeadm 也不会为之生成备份版本。
4. 问题排查
在升级k8s 从1.25.12到1.26.7的过程中,遇到master节点的服务起不来,报错如下:
"CreatePodSandbox for pod failed" err="open /run/systemd/resolve/resolv.conf: no such file or directory" pod="kube-system/kube-apiserver"
现象主要是静态pod起不来,包括etcd等。
具体解决方法参考:open /run/systemd/resolve/resolv.conf
# 查看以下的resolv.conf是否存在
cat /var/lib/kubelet/config.yaml | grep resolvConf
/run/systemd/resolve/resolv.conf
# 如果不存在,检查systemd-resolved是否正常运行,
systemctl status systemd-resolved
# 如果没有运行,则运行该服务
systemctl start systemd-resolved
# 或者新建文件/run/systemd/resolve/resolv.conf,并将其他master的文件拷贝过来。
参考:
2.3 - k8s证书及秘钥
1. 证书分类
-
服务器证书:server cert,用于客户端验证服务端的身份。
-
客户端证书:client cert,用于服务端验证客户端的身份。
-
对等证书:peer cert(既是
server cert又是client cert),用户成员之间的身份验证,例如 etcd。
1.1. k8s集群的证书分类
etcd节点:需要标识自己服务的server cert,也需要client cert与etcd集群其他节点交互,因此需要一个对等证书。master节点:需要标识 apiserver服务的server cert,也需要client cert连接etcd集群,也需要一个对等证书。kubelet:需要标识自己服务的server cert,也需要client cert请求apiserver,也使用一个对等证书。kubectl、kube-proxy、calico:需要client证书。
2. CA证书及秘钥
目录:/etc/kubernetes/ssl
| 分类 | 证书/秘钥 | 说明 | 组件 |
|---|---|---|---|
| ca | ca-key.pem | ||
| ca.pem | |||
| ca.csr | |||
| Kubernetes | kubernetes-key.pem | ||
| kubernetes.pem | |||
| kubernetes.csr | |||
| Admin | admin-key.pem | ||
| admin.pem | |||
| admin.csr | |||
| Kubelet | kubelet.crt | ||
| kubelet.key |
配置文件
| 分类 | 证书/秘钥 | 说明 |
|---|---|---|
| ca | ca-config.json | |
| ca-csr.json | ||
| Kubernetes | kubernetes-csr.json | |
| Admin | admin-csr.json | |
| Kube-proxy | kube-proxy-csr.json |
3. cfssl工具
安装cfssl:
# 下载cfssl
$ curl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -o /usr/local/bin/cfssl
$ curl https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 -o /usr/local/bin/cfssljson
$ curl https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 -o /usr/local/bin/cfssl-certinfo
# 添加可执行权限
$ chmod +x /usr/local/bin/cfssl /usr/local/bin/cfssljson /usr/local/bin/cfssl-certinfo
4. 创建 CA (Certificate Authority)
4.1. 配置源文件
创建 CA 配置文件
ca-config.json
cat << EOF > ca-config.json
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "876000h"
}
}
}
}
EOF
参数说明
ca-config.json:可以定义多个 profiles,分别指定不同的过期时间、使用场景等参数;后续在签名证书时使用某个 profile;signing:表示该证书可用于签名其它证书;生成的 ca.pem 证书中CA=TRUE;server auth:表示client可以用该 CA 对server提供的证书进行验证;client auth:表示server可以用该CA对client提供的证书进行验证;
创建 CA 证书签名请求
ca-csr.json
cat << EOF > ca-csr.json
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "ShenZhen",
"L": "ShenZhen",
"O": "k8s",
"OU": "System"
}
]
}
EOF
参数说明
ca-csr.json的参数
- CN:
Common Name,kube-apiserver 从证书中提取该字段作为请求的用户名 (User Name);浏览器使用该字段验证网站是否合法;
names中的字段:
- C : country,国家
- ST: state,州或省份
- L:location,城市
- O:organization,组织,kube-apiserver 从证书中提取该字段作为请求用户所属的组 (Group)
- OU:organization unit
4.2. 执行命令
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
输出如下:
# cfssl gencert -initca ca-csr.json | cfssljson -bare ca
2019/12/13 14:35:52 [INFO] generating a new CA key and certificate from CSR
2019/12/13 14:35:52 [INFO] generate received request
2019/12/13 14:35:52 [INFO] received CSR
2019/12/13 14:35:52 [INFO] generating key: rsa-2048
2019/12/13 14:35:52 [INFO] encoded CSR
2019/12/13 14:35:52 [INFO] signed certificate with serial number 248379771349454958117219047414671162179070747780
生成以下文件:
# 生成文件
-rw-r--r-- 1 root root 1005 12月 13 11:32 ca.csr
-rw------- 1 root root 1675 12月 13 11:32 ca-key.pem
-rw-r--r-- 1 root root 1363 12月 13 11:32 ca.pem
# 配置源文件
-rw-r--r-- 1 root root 293 12月 13 11:31 ca-config.json
-rw-r--r-- 1 root root 210 12月 13 11:31 ca-csr.json
5. 创建 kubernetes 证书
5.1. 配置源文件
创建 kubernetes 证书签名请求文件kubernetes-csr.json。
cat << EOF > kubernetes-csr.json
{
"CN": "kubernetes",
"hosts": [
"127.0.0.1",
"<MASTER_IP>",
"<MASTER_CLUSTER_IP>",
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
"kubernetes.default.svc.cluster",
"kubernetes.default.svc.cluster.local"
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [{
"C": "<country>",
"ST": "<state>",
"L": "<city>",
"O": "<organization>",
"OU": "<organization unit>"
}]
}
EOF
参数说明:
MASTER_IP:master节点的IP或域名MASTER_CLUSTER_IP:kube-apiserver指定的service-cluster-ip-range网段的第一个IP,例如(10.254.0.1)。
5.2. 执行命令
$ cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kubernetes-csr.json | cfssljson -bare kubernetes
输出如下:
# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kubernetes-csr.json | cfssljson -bare kubernetes
2019/12/13 14:40:28 [INFO] generate received request
2019/12/13 14:40:28 [INFO] received CSR
2019/12/13 14:40:28 [INFO] generating key: rsa-2048
2019/12/13 14:40:28 [INFO] encoded CSR
2019/12/13 14:40:28 [INFO] signed certificate with serial number 392795299385191732458211386861696542628305189374
2019/12/13 14:40:28 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").
生成以下文件:
# 生成文件
-rw-r--r-- 1 root root 1269 12月 13 14:40 kubernetes.csr
-rw------- 1 root root 1679 12月 13 14:40 kubernetes-key.pem
-rw-r--r-- 1 root root 1643 12月 13 14:40 kubernetes.pem
# 配置源文件
-rw-r--r-- 1 root root 580 12月 13 14:40 kubernetes-csr.json
6. 创建 admin 证书
6.1. 配置源文件
创建 admin 证书签名请求文件 admin-csr.json:
cat << EOF > admin-csr.json
{
"CN": "admin",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "ShenZhen",
"L": "ShenZhen",
"O": "system:masters",
"OU": "System"
}
]
}
EOF
6.2. 执行命令
$ cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes admin-csr.json | cfssljson -bare admin
输出如下:
# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes admin-csr.json | cfssljson -bare admin
2019/12/13 14:52:37 [INFO] generate received request
2019/12/13 14:52:37 [INFO] received CSR
2019/12/13 14:52:37 [INFO] generating key: rsa-2048
2019/12/13 14:52:37 [INFO] encoded CSR
2019/12/13 14:52:37 [INFO] signed certificate with serial number 465422983473444224050765004141217688748259757371
2019/12/13 14:52:37 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").
生成文件
# 生成文件
-rw-r--r-- 1 root root 1013 12月 13 14:52 admin.csr
-rw------- 1 root root 1675 12月 13 14:52 admin-key.pem
-rw-r--r-- 1 root root 1407 12月 13 14:52 admin.pem
# 配置源文件
-rw-r--r-- 1 root root 231 12月 13 14:49 admin-csr.json
7. 创建 kube-proxy 证书
7.1. 配置源文件
创建 kube-proxy 证书签名请求文件 kube-proxy-csr.json:
cat << EOF > kube-proxy-csr.json
{
"CN": "system:kube-proxy",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}
]
}
EOF
7.2. 执行命令
$ cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy
输出如下:
# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy
2019/12/13 19:37:48 [INFO] generate received request
2019/12/13 19:37:48 [INFO] received CSR
2019/12/13 19:37:48 [INFO] generating key: rsa-2048
2019/12/13 19:37:48 [INFO] encoded CSR
2019/12/13 19:37:48 [INFO] signed certificate with serial number 526712749765692443642491255093816136154324531741
2019/12/13 19:37:48 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").
生成文件:
# 生成文件
-rw-r--r-- 1 root root 1009 12月 13 19:37 kube-proxy.csr
-rw------- 1 root root 1675 12月 13 19:37 kube-proxy-key.pem
-rw-r--r-- 1 root root 1407 12月 13 19:37 kube-proxy.pem
# 配置源文件
-rw-r--r-- 1 root root 230 12月 13 19:37 kube-proxy-csr.json
8. 校验证书
openssl x509 -noout -text -in kubernetes.pem
输出如下:
# openssl x509 -noout -text -in kubernetes.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
44:cd:8c:e6:a4:60:ff:3f:09:af:02:e7:68:5e:f2:0f:e6:a0:39:fe
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=CN, ST=ShenZhen, L=ShenZhen, O=k8s, OU=System, CN=kubernetes
Validity
Not Before: Dec 13 06:35:00 2019 GMT
Not After : Nov 19 06:35:00 2119 GMT
Subject: C=CN, ST=ShenZhen, L=ShenZhen, O=k8s, OU=System, CN=kubernetes
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:d7:91:4f:90:56:fb:ab:a9:de:c4:98:9e:d7:e6:
45:db:5a:14:9a:76:78:6a:4c:db:3c:47:3c:e7:1c:
3c:37:4e:8a:cf:9c:a1:8a:4c:51:4c:cd:45:b0:03:
87:06:b9:20:2c:3a:51:f9:21:55:1c:90:7c:f8:93:
bc:6a:48:05:3d:8b:74:fd:f2:f1:e6:5e:ad:b4:a8:
f6:6d:f9:63:9e:e4:b4:cc:68:9e:90:d7:ef:de:ce:
c1:1d:1b:68:59:68:5e:5f:7d:5c:f3:49:4f:18:72:
be:b5:c8:af:e2:8d:34:9c:d2:68:b7:8c:26:69:cc:
a5:f4:ca:69:2d:d7:21:f5:19:2e:b2:b5:97:16:87:
9f:9c:fd:01:97:c2:0e:20:b4:88:27:9a:37:9a:af:
0a:cf:82:4f:26:24:cb:07:ac:8c:b1:34:20:42:22:
00:b2:b0:98:c5:53:01:fb:32:aa:15:1b:7e:39:44:
ae:af:6e:c3:65:96:f6:38:7a:87:37:d0:31:63:d8:
a4:15:13:f2:56:da:e6:09:45:2b:46:2c:cb:63:db:
f7:ba:7f:44:0a:36:39:7c:cc:5b:42:e5:56:c7:7f:
dd:64:5c:f2:4a:af:d3:a9:d1:6e:06:27:57:09:4d:
db:08:62:87:66:c8:2c:36:00:41:f1:90:f6:5f:68:
20:3d
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Key Identifier:
3D:3F:FA:B8:36:D7:FE:B1:59:BE:B1:F5:C1:5D:88:3D:BC:78:9F:87
X509v3 Authority Key Identifier:
keyid:40:A2:D4:30:22:12:2E:C2:FB:A2:55:2C:CB:F0:F6:3E:4D:B8:02:03
X509v3 Subject Alternative Name:
DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster, DNS:kubernetes.default.svc.cluster.local, IP Address:127.0.0.1, IP Address:172.20.0.112, IP Address:172.20.0.113, IP Address:172.20.0.114, IP Address:172.20.0.115, IP Address:10.254.0.1
Signature Algorithm: sha256WithRSAEncryption
63:50:f6:2a:03:c7:35:dd:e9:10:8d:2f:b3:27:9a:64:f3:e1:
11:8a:18:1e:fa:6d:85:30:11:b4:59:a3:6c:86:cd:2b:5c:67:
17:4f:aa:0d:bb:4c:ee:c8:af:e7:3d:61:6d:03:9d:14:6f:00:
48:56:59:b5:76:13:a9:30:23:e0:b2:d2:12:64:0c:60:0d:76:
ec:c6:4f:b1:bc:24:01:7a:48:c6:fd:9e:5d:68:da:b9:a1:ad:
30:7a:ba:90:e2:e3:4e:b4:92:1b:c5:f2:8c:c1:b0:3d:fc:14:
d2:46:e8:f8:22:8f:d9:4d:85:4f:58:6b:0f:84:78:06:b4:b9:
92:b9:0d:bd:1d:95:e9:0d:42:d3:fd:dd:2a:59:60:3f:63:35:
eb:07:25:d2:ea:0d:19:a6:f3:dc:92:8e:ee:73:04:15:5e:97:
e8:da:51:c3:69:49:96:36:c7:cc:5b:e5:e5:cb:e5:ce:9f:21:
6f:6b:56:16:bf:85:ad:1c:8c:91:c1:91:0a:90:18:e2:4a:b0:
32:58:33:ef:55:8e:8f:4a:e3:0f:b8:f7:41:04:65:89:e1:1b:
d8:41:28:6e:84:c3:1c:8e:a9:a0:8a:42:e4:fe:d7:fe:0e:24:
dc:74:37:fa:5e:be:20:69:c5:9a:5a:e6:83:1c:0b:9e:e1:43:
ef:4f:7a:37
字段说明:
- 确认
Issuer字段的内容和ca-csr.json一致; - 确认
Subject字段的内容和kubernetes-csr.json一致; - 确认
X509v3 Subject Alternative Name字段的内容和kubernetes-csr.json一致; - 确认
X509v3 Key Usage、Extended Key Usage字段的内容和ca-config.json中kubernetesprofile 一致;
9. 分发证书
将生成的证书和秘钥文件(后缀名为.pem)拷贝到所有机器的 /etc/kubernetes/ssl 目录下。
mkdir -p /etc/kubernetes/ssl
cp *.pem /etc/kubernetes/ssl
参考文章:
2.4 - kubeadm管理证书
通过kubeadm搭建的集群默认的证书时间是1年(由于官方期望每年更新一次k8s的版本,在更新的时候会默认更新证书),当你执行命令出现以下报错,说明你的证书已经到期了,则需要手动更新证书。
# kubectl get node
Unable to connect to the server: x509: certificate has expired or is not yet valid: current time 2023-08-03T18:06:23+08:00 is after 2023-07-04T06:30:54Z
# 或者出现以下报错
You must be logged in to the server(unauthorized)
以下说明手动更新证书的流程。
具体可以参考:使用 kubeadm 进行证书管理 | Kubernetes
1. 检查证书是否过期
kubeadm certs check-expiration
会输出以下的内容:
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Dec 30, 2020 23:36 UTC 364d no
apiserver Dec 30, 2020 23:36 UTC 364d ca no
apiserver-etcd-client Dec 30, 2020 23:36 UTC 364d etcd-ca no
apiserver-kubelet-client Dec 30, 2020 23:36 UTC 364d ca no
controller-manager.conf Dec 30, 2020 23:36 UTC 364d no
etcd-healthcheck-client Dec 30, 2020 23:36 UTC 364d etcd-ca no
etcd-peer Dec 30, 2020 23:36 UTC 364d etcd-ca no
etcd-server Dec 30, 2020 23:36 UTC 364d etcd-ca no
front-proxy-client Dec 30, 2020 23:36 UTC 364d front-proxy-ca no
scheduler.conf Dec 30, 2020 23:36 UTC 364d no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Dec 28, 2029 23:36 UTC 9y no
etcd-ca Dec 28, 2029 23:36 UTC 9y no
front-proxy-ca Dec 28, 2029 23:36 UTC 9y no
2. 手动更新过期的证书
分别在master节点执行以下命令。
2.1. 备份/etc/kubernetes目录
cp -fr /etc/kubernetes /etc/kubernetes.bak
cp -fr ~/.kube ~/.kube.bak
2.2. 执行更新证书命令
kubeadm certs renew all
输出如下:
# kubeadm certs renew all
[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[renew] Error reading configuration from the Cluster. Falling back to default configuration
certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed
Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager, kube-scheduler and etcd, so that they can use the new certificates.
2.3. 重启k8s组件
先重启etcd
注意事项:需要将三个master节点的证书都重新更新后,然后三个master的etcd服务一起重启,使得etcd集群使用新的证书可以正常运行,否则会导致kube-apiserver也启动失败。
crictl ps |grep "etcd"|awk '{print $1}'|xargs crictl stop
再重启kube-apiserver、kube-controller、kube-scheduler容器。
crictl ps |egrep "kube-apiserver|kube-scheduler|kube-controller"|awk '{print $1}'|xargs crictl stop
2.4. 更新默认的kubeconfig文件
cp -fr /etc/kubernetes/admin.conf $HOME/.kube/config
2.5. 配置kubelet证书轮转
由于kubelet默认支持证书轮转,当证书过期时,可以自动生成新的密钥,并从 Kubernetes API 申请新的证书。可以查看kubelet的配置检查是否已经开启。
# cat /var/lib/kubelet/config.yaml |grep rotate
rotateCertificates: true
3. 修改kubeadm源码证书时间
由于社区不允许用户配置超过1年的证书,因此自定义证书时间的参数不被允许开发。
相关issue如下:
如果要实现自定义参数设置证书时间,可参考一下pr:
如果需要修改kubeadm源码证书可以参考如下代码修改。
kubeadm中跟证书相关的代码有:
3.1. ca文件的有效期(默认为10年)
代码文件:./staging/src/k8s.io/client-go/util/cert/cert.go 中 NewSelfSignedCACert 函数的NotAfter字段
代码如下:
// NewSelfSignedCACert creates a CA certificate
func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, error) {
now := time.Now()
// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
if err != nil {
return nil, err
}
serial = new(big.Int).Add(serial, big.NewInt(1))
notBefore := now.UTC()
if !cfg.NotBefore.IsZero() {
notBefore = cfg.NotBefore.UTC()
}
tmpl := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: cfg.CommonName,
Organization: cfg.Organization,
},
DNSNames: []string{cfg.CommonName},
NotBefore: notBefore,
NotAfter: now.Add(duration365d * 10).UTC(), # 默认为10年
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
}
certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key)
if err != nil {
return nil, err
}
return x509.ParseCertificate(certDERBytes)
}
3.2. 证书文件的有效期(默认为1年)
代码文件:cmd/kubeadm/app/util/pkiutil/pki_helpers.go中 NewSignedCert 函数的 notAfter 字段
- 常量参数kubeadmconstants.
CertificateValidity: /cmd/kubeadm/app/constants/constants.go
代码如下:
// NewSignedCert creates a signed certificate using the given CA certificate and key
func NewSignedCert(cfg *CertConfig, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer, isCA bool) (*x509.Certificate, error) {
// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
if err != nil {
return nil, err
}
serial = new(big.Int).Add(serial, big.NewInt(1))
if len(cfg.CommonName) == 0 {
return nil, errors.New("must specify a CommonName")
}
keyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature
if isCA {
keyUsage |= x509.KeyUsageCertSign
}
RemoveDuplicateAltNames(&cfg.AltNames)
# 此处引用了一个常量
notAfter := time.Now().Add(kubeadmconstants.CertificateValidity).UTC()
if cfg.NotAfter != nil {
notAfter = *cfg.NotAfter
}
certTmpl := x509.Certificate{
Subject: pkix.Name{
CommonName: cfg.CommonName,
Organization: cfg.Organization,
},
DNSNames: cfg.AltNames.DNSNames,
IPAddresses: cfg.AltNames.IPs,
SerialNumber: serial,
NotBefore: caCert.NotBefore,
NotAfter: notAfter,
KeyUsage: keyUsage,
ExtKeyUsage: cfg.Usages,
BasicConstraintsValid: true,
IsCA: isCA,
}
certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &certTmpl, caCert, key.Public(), caKey)
if err != nil {
return nil, err
}
return x509.ParseCertificate(certDERBytes)
}
其中常量文件为:
- /cmd/kubeadm/app/constants/constants.go
代码如下:
# 常量默认证书为1年。
// CertificateValidity defines the validity for all the signed certificates generated by kubeadm
CertificateValidity = time.Hour * 24 * 365
可以修改此处常量的值10年,例如:
# 常量默认证书为1年。
// CertificateValidity defines the validity for all the signed certificates generated by kubeadm
CertificateValidity = time.Hour * 24 * 365 * 10
修改源码后,就可以重新编译kubeadm二进制。生成10年的证书文件。
参考:
2.5 - k8s版本说明
1. k8s版本号说明
k8s维护最新三个版本的发布分支([2022.7.2]当前最新三个版本为1.24、1.23、1.22),Kubernetes 1.19 和更新的版本获得大约 1 年的补丁支持。
Kubernetes 版本表示为 x.y.z, 其中 x 是主要版本,y 是次要版本,z 是补丁版本。遵循语义化版本规范。
2. 版本偏差策略
2.1. 支持的版本偏差
总结:
-
kubelet版本不能比kube-apiserver版本新,最多只可落后两个次要版本。 -
kube-controller-manager、kube-scheduler和cloud-controller-manager不能比kube-apiserver版本新。最多落后一个次要版本(允许实时升级)。 -
kubectl在kube-apiserver的一个次要版本(较旧或较新)中支持。 -
kube-proxy和节点上的kubelet必须是相同的次要版本。
1)kube-apiserver
在高可用性(HA)集群中, 最新版和最老版的 kube-apiserver 实例版本偏差最多为一个次要版本。
例如:
- 最新的
kube-apiserver实例处于 1.24 版本 - 其他
kube-apiserver实例支持 1.24 和 1.23 版本
2)kubelet
kubelet 版本不能比 kube-apiserver 版本新,并且最多只可落后两个次要版本。
例如:
kube-apiserver处于 1.24 版本kubelet支持 1.24、1.23 和 1.22 版本
说明:
如果 HA 集群中的 kube-apiserver 实例之间存在版本偏差,这会缩小允许的 kubelet 版本范围。
例如:
kube-apiserver实例处于 1.24 和 1.23 版本kubelet支持 1.23 和 1.22 版本, (不支持 1.24 版本,因为这将比kube-apiserver1.23 版本的实例新)
3)kube-controller-manager、kube-scheduler 和 cloud-controller-manager
kube-controller-manager、kube-scheduler 和 cloud-controller-manager 不能比与它们通信的 kube-apiserver 实例新。 它们应该与 kube-apiserver 次要版本相匹配,但可能最多旧一个次要版本(允许实时升级)。
例如:
kube-apiserver处于 1.24 版本kube-controller-manager、kube-scheduler和cloud-controller-manager支持 1.24 和 1.23 版本
说明:
如果 HA 集群中的 kube-apiserver 实例之间存在版本偏差, 并且这些组件可以与集群中的任何 kube-apiserver 实例通信(例如,通过负载均衡器),这会缩小这些组件所允许的版本范围。
例如:
kube-apiserver实例处于 1.24 和 1.23 版本kube-controller-manager、kube-scheduler和cloud-controller-manager与可以路由到任何kube-apiserver实例的负载均衡器通信kube-controller-manager、kube-scheduler和cloud-controller-manager支持 1.23 版本(不支持 1.24 版本,因为它比 1.23 版本的kube-apiserver实例新)
4)kubectl
kubectl 在 kube-apiserver 的一个次要版本(较旧或较新)中支持。
例如:
kube-apiserver处于 1.24 版本kubectl支持 1.25、1.24 和 1.23 版本
说明:
如果 HA 集群中的 kube-apiserver 实例之间存在版本偏差,这会缩小支持的 kubectl 版本范围。
例如:
kube-apiserver实例处于 1.24 和 1.23 版本kubectl支持 1.24 和 1.23 版本(其他版本将与kube-apiserver组件之一相差不止一个的次要版本)
5)kube-proxy
kube-proxy和节点上的kubelet必须是相同的次要版本。kube-proxy版本不能比kube-apiserver版本新。kube-proxy最多只能比kube-apiserver落后两个次要版本。
例如:
如果 kube-proxy 版本处于 1.22 版本:
kubelet必须处于相同的次要版本 1.22。kube-apiserver版本必须介于 1.22 和 1.24 之间,包括两者。
2.2. 组件升级顺序
优先升级kube-apiserver,其他的组件按照上述的版本要求进行升级,最好保持一致的版本。
3. k8s版本发布周期
k8s每年大概发布三次,即3-4个月发布一次大版本(发布版本为 vX.Y 里程碑创建的 Git 分支 release-X.Y)。
发布过程可被认为具有三个主要阶段:
- 特性增强定义
- 实现
- 稳定
3.1. 发布周期
1)正常开发(第 1-11 周)
-
/sig {name}
-
/sig {name}
-
/kind {type}
-
/lgtm
-
/approved
2)代码冻结(第 12-14 周)
- /milestone {v1.y}
- /sig {name}
- /kind {bug, failing-test}
- /lgtm
- /approved
3)发布后(第 14 周以上)
回到“正常开发”阶段要求:
- /sig {name}
- /kind {type}
- /lgtm
- /approved
参考:
2.6 - k8s版本记录
1.27
参考:
(一)重要更新
k8s.gcr.io 重定向到 registry.k8s.io 相关说明
Kubernetes 项目为了托管其容器镜像,使用社区拥有的一个名为 registry.k8s.io. 的镜像仓库。从 3 月 20 日起,所有来自过期 k8s.gcr.io 仓库的流量将被重定向到 registry.k8s.io。 已弃用的 k8s.gcr.io 仓库最终将被淘汰。Kubernetes v1.27 版本不会发布到旧的仓库。
原地调整 Pod 资源 (alpha)
参考:Kubernetes 1.27: 原地调整 Pod 资源 (alpha) | Kubernetes
在 Kubernetes v1.27 中,添加了一个新的 alpha 特性,允许用户调整分配给 Pod 的 CPU 和内存资源大小,而无需重新启动容器。 首先,API 层面现在允许修改 Pod 容器中的 resources 字段下的 cpu 和 memory 资源。资源修改只需 patch 正在运行的 pod 规约即可。
StatefulSet PVC 自动删除功能特性 Beta
在 v1.23 中引入的 StatefulSetAutoDeletePVC 功能将在 1.27 版本中升级为 Beta,并默认开启。 然而,默认开启并不意味着所有 StatefulSet 的 PVC 都将自动删除。
优化大型集群中 kube-proxy 的 iptables 模式性能
功能 MinimizeIPTablesRestore 在 1.26 版本中引入,并在 1.27 版本中升级为 Beta 并默认启用。 该功能旨在改善大型集群中 kube-proxy 的 iptables 模式性能。
如果您遇到 Service 信息未正确同步到 iptables 的问题,您可以通过将 kube-proxy 启动参数设置为 --feature-gates=MinimizeIPTablesRestore=false 来禁用该功能(并向社区提交问题)。 您还可以查看 kube-proxy 的 metrics 信息中的 sync_proxy_rules_iptables_partial_restore_failures_total 指标来监控规则同步失败的次数。
Kubelet 事件驱动 PLEG 升级为 Beta
在节点 Pod 较多的情况下,通过容器运行时的 Event 驱动 Pod 状态更新,能够有效地提升效率。 在 1.27 中,该功能已经达到了 Beta 条件,基础的 E2E 测试任务已经添加。 之所以默认关闭该功能,是因为社区认为该功能还需要补充以下验证:压力测试、恢复测试和带退避逻辑的重试。
Pod 调度就绪态功能增强
调度就绪态功能 PodSchedulingReadiness,在 v1.26 作为 Alpha 功能引入,从 v1.27 开始该功能升级为 Beta,默认开启。
Deployment 滚动更新过程中的调度优化
在 v1.27 中,PodTopologySpread 调度策略可以区分调度 Pod 标签的值 (这里通常指 Pod 的 pod-template-hash 标签,不同 replica set 对应的 Pod 该标签的值不同), 这样滚动更新后,新的 Pod 实例会被调度得更加均匀 。
关于加快 Pod 启动的进展
要启用并行镜像拉取,请在 kubelet 配置中将 serializeImagePulls 字段设置为 false。 当 serializeImagePulls 被禁用时,将立即向镜像服务发送镜像拉取请求,并可以并行拉取多个镜像。
为了在节点上具有多个 Pod 的场景中加快 Pod 启动,特别是在突然扩缩的情况下, kubelet 需要同步 Pod 状态并准备 ConfigMap、Secret 或卷。这就需要大带宽访问 kube-apiserver。在 v1.27 中,kubelet 为了提高 Pod 启动性能,将这些默认值分别提高到了 50 和 100。
(二)弃用变更
-
kubelet 移除了命令行参数 --container-runtime。
-
弃用的命令行参数
--pod-eviction-timeout将被从 kube-controller-manager 中移除。
1.26
参考:
(一)重要更新
Kubelet Evented PLEG for Better Performance
该功能让 kubelet 在跟踪节点中 Pod 状态时,通过尽可能依赖容器运行时接口(CRI) 的通知来减少定期轮训,这会减少 kubelet 对 CPU 的使用
新增 Alpha Feature Gate —— EventedPLEG 来控制是否开启该功能。
优化 kube-proxy 性能,它只发送在调用 iptables-restore 中更改的规则,而不是整个规则集
PR#112200 client-go 的 SharedInformerFactory 增加 Shutdown 方法,来等待 Factory 内所有运行的 informer 都结束。
(二)弃用变更
- Kubelet 不再支持 v1alpha2 版本的 CRI,接入的容器运行时必须实现 v1 版本的容器运行时接口。
Kubernetes v1.26 将不支持 containerd 1.5.x 及更早的版本;需要升级到 containerd 1.6.x 或更高版本后,才能将该节点的 kubelet 升级到 1.26。
1.25
参考:
(一)重要更新
cgroup v2 升级到 GA
Kubernetes 1.25 将 cgroup v2 正式发布(GA), 让kubelet使用最新的容器资源管理能力。一些 Kubernetes 特性专门使用 cgroup v2 来增强资源管理和隔离。 例如,MemoryQoS 特性提高了内存利用率并依赖 cgroup v2 功能来启用它。kubelet 中的新资源管理特性也将利用新的 cgroup v2 特性向前发展。
CSI 内联存储卷正式发布GA
CSI 内联存储卷与其他类型的临时卷相似,如 configMap、downwardAPI 和 secret。 重要的区别是,存储是由 CSI 驱动提供的,它允许使用第三方供应商提供的临时存储。 卷被定义为 Pod 规约的一部分,并遵循 Pod 的生命周期,这意味着卷随着 Pod 的调度而创建,并随着 Pod 的销毁而销毁。
Ephemeral Containers进入稳定版本
当pod crash的时候,无法通过kubectl exec 进入容器,这个时候可以通过临时容器[Ephemeral Containers](临时容器 | Kubernetes)
(二)弃用变更
-
Kubernetes v1.25 将移除 PodSecurityPolicy,取而代之的是 Pod Security Admission(即 PodSecurity 安全准入控制器)。
-
-
从 v1.25 开始,Kubelet 将逐渐迁移为不在
nat表中创建以下 iptables 链:KUBE-MARK-DROPKUBE-MARK-MASQKUBE-POSTROUTING
-
1.24
最新发行版本:1.24.2 (发布日期: 2022-06-15)
不再支持:2023-09-29
Complete 1.24 Schedule and Changelog
Kubernetes 1.24 使用 go1.18构建,默认情况下将不再验证使用 SHA-1 哈希算法签名的证书。
(一)重要更新
1.24.0主要参考kubernetes/CHANGELOG-1.24.major-themes
1)kubelet完全移除Dockershim【最重大更新】
在 v1.20 中弃用后,dockershim 组件已从 kubelet 中删除。从 v1.24 开始,您将需要使用其他受支持的运行时之一(例如 containerd 或 CRI-O),或者如果您依赖 Docker 引擎作为容器运行时,则使用 cri-dockerd。有关确保您的集群已准备好进行此移除的更多信息,请参阅本[指南](Is Your Cluster Ready for v1.24? | Kubernetes)。
2)Beta API 默认关闭
默认情况下,不会在集群中启用新的 beta API。默认情况下,现有的 beta API 和现有 beta API 的新版本将继续启用。
3)存储容量和卷扩展到GA
存储容量跟踪支持通过 CSIStorageCapacity 对象公开当前可用的存储容量,并增强使用具有后期绑定的 CSI 卷的 pod 的调度。
卷扩展增加了对调整现有持久卷大小的支持。
4)避免 IP 分配给service的冲突
Kubernetes 1.24 引入了一项新的选择加入功能,允许您为服务的静态 IP 地址分配软预留范围。通过手动启用此功能,集群将更喜欢从服务 IP 地址池中自动分配,从而降低冲突风险。
可以分配 Service ClusterIP:
-
动态,这意味着集群将自动在配置的服务 IP 范围内选择一个空闲 IP。
-
静态,这意味着用户将在配置的服务 IP 范围内设置一个 IP。
Service ClusterIP 是唯一的,因此,尝试使用已分配的 ClusterIP 创建 Service 将返回错误。
(二)弃用变更
1)kubeadm
-
kubeadm.k8s.io/v1beta2 已被弃用,并将在未来的版本中删除,可能在 3 个版本(一年)中。您应该开始将 kubeadm.k8s.io/v1beta3 用于新集群。要迁移磁盘上的旧配置文件,您可以使用 kubeadm config migrate 命令。
-
默认 kubeadm 配置为 containerd 套接字(Unix:unix:///var/run/containerd/containerd.sock,Windows:npipe:////./pipe/containerd-containerd)而不是 Docker 的配置.如果在集群创建期间 Init|JoinConfiguration.nodeRegistration.criSocket 字段为空,并且在主机上发现多个套接字,则总是会抛出错误并要求用户通过设置字段中的值来指定要使用的套接字。使用 crictl 与 CRI 套接字进行所有通信,以执行诸如拉取图像和获取正在运行的容器列表等操作,而不是在 Docker 的情况下使用 docker CLI。
-
kubeadm 迁移到标签和污点中不再使用 master 一词。对于新的集群,标签 node-role.kubernetes.io/master 将不再添加到控制平面节点,只会添加标签 node-role.kubernetes.io/control-plane。
2)kube-apiserver
-
不安全的地址标志 --address、--insecure-bind-address、--port 和 --insecure-port(自 1.20 起惰性)被删除
-
弃用了--master-countflag 和--endpoint-reconciler-type=master-countreconciler,转而使用 lease reconciler。
-
已弃用Service.Spec.LoadBalancerIP。
3)kube-controller-manager
- kube-controller-manager 中的不安全地址标志 --address 和 --port 自 v1.20 起无效,并在 v1.24 中被删除。
4)kubelet
-
--pod-infra-container-image kubelet 标志已弃用,将在未来版本中删除。
-
以下与 dockershim 相关的标志也与 dockershim 一起被删除 --experimental-dockershim-root-directory、--docker-endpoint、--image-pull-progress-deadline、--network-plugin、--cni-conf -dir,--cni-bin-dir,--cni-cache-dir,--network-plugin-mtu。(#106907, @cyclinder)
1.23
最新发行版本:1.23.8 (发布日期: 2022-06-15)
不再支持:2023-02-28
补丁版本: 1.23.1、 1.23.2、 1.23.3、 1.23.4、 1.23.5、 1.23.6、 1.23.7、 1.23.8
Complete 1.23 Schedule and Changelog
Kubernetes 是使用 golang 1.17 构建的。此版本的 go 删除了使用 GODEBUG=x509ignoreCN=0 环境设置来重新启用将 X.509 服务证书的 CommonName 视为主机名的已弃用旧行为的能力。
(一) 重要更新
1)FlexVolume 已弃用
FlexVolume 已弃用。 Out-of-tree CSI 驱动程序是在 Kubernetes 中编写卷驱动程序的推荐方式。FlexVolume 驱动程序的维护者应实施 CSI 驱动程序并将 FlexVolume 的用户转移到 CSI。 FlexVolume 的用户应将其工作负载转移到 CSI 驱动程序。
2)IPv4/IPv6 双栈网络到 GA
IPv4/IPv6 双栈网络从 GA 毕业。从 1.21 开始,Kubernetes 集群默认启用支持双栈网络。在 1.23 中,移除了 IPv6DualStack 功能门。双栈网络的使用不是强制性的。尽管启用了集群以支持双栈网络,但 Pod 和服务继续默认为单栈。要使用双栈网络:Kubernetes 节点具有可路由的 IPv4/IPv6 网络接口,使用支持双栈的 CNI 网络插件,Pod 配置为双栈,服务的 .spec.ipFamilyPolicy 字段设置为 PreferDualStack 或需要双栈。
3)HorizontalPodAutoscaler v2 到 GA
HorizontalPodAutoscaler API 的第 2 版在 1.23 版本中逐渐稳定。 HorizontalPodAutoscaler autoscaling/v2beta2 API 已弃用,取而代之的是新的 autoscaling/v2 API,Kubernetes 项目建议将其用于所有用例。
4)Scheduler简化多点插件配置
kube-scheduler 正在为插件添加一个新的、简化的配置字段,以允许在一个位置启用多个扩展点。新的 multiPoint 插件字段旨在为管理员简化大多数调度程序设置。通过 multiPoint 启用的插件将自动为它们实现的每个单独的扩展点注册。例如,实现 Score 和 Filter 扩展的插件可以同时为两者启用。这意味着可以启用和禁用整个插件,而无需手动编辑单个扩展点设置。这些扩展点现在可以被抽象出来,因为它们与大多数用户无关。
(二)已知问题
在 1.22 Kubernetes 版本附带的 etcd v3.5.0 版本中发现了数据损坏问题。请阅读 etcd 的最新[生产建议](etcd/CHANGELOG at main · etcd-io/etcd · GitHub)。
运行etcd v3.5.2 v3.5.1和v3.5.0高负荷会导致数据损坏问题。如果etcd进程被杀,偶尔有些已提交的事务并不反映在所有的成员。建议升级到v3.5.3。
最低推荐etcd版本运行在生产3.3.18 + 3.4.2 + v3.5.3 +。
1.22
最新发行版本:1.22.11 (发布日期: 2022-06-15)
不再支持:2022-10-28
补丁版本: 1.22.1、 1.22.2、 1.22.3、 1.22.4、 1.22.5、 1.22.6、 1.22.7、 1.22.8、 1.22.9、 1.22.10、 1.22.11
Complete 1.22 Schedule and Changelog
(—)重要更新
1)kubeadm
-
允许非root用户允许kubeadm。
-
现在V1beta3首选API版本;v1beta2 API也仍然是可用的,并没有弃用。
-
移除对docker cgroup driver的检查,kubeadm默认使用systemd cgroup driver,需要手动将runtime配置为systemd。
-
v1beta3中删除ClusterConfiguration.DNS字段,因为CoreDNS是唯一支持DNS类型。
2)etcd
- etcd使用v3.5.0版本。(但是在1.23版本中发现v3.5.0有数据损坏的问题)
3)kubelet
-
节点支持swap内存。
-
作为α特性,Kubernetes v1.22并且可以使用cgroup v2 API来控制内存分配和隔离。这个功能的目的是改善工作负载和节点可用性时对内存资源的争用。
参考:
3 - 基本概念
3.1 - kubernetes架构
3.1.1 - Kubernetes总架构图
1. Kubernetes的总架构图
2. Kubernetes各个组件介绍
2.1 kube-master[控制节点]
master的工作流程图
- Kubecfg将特定的请求,比如创建Pod,发送给Kubernetes Client。
- Kubernetes Client将请求发送给API server。
- API Server根据请求的类型,比如创建Pod时storage类型是pods,然后依此选择何种REST Storage API对请求作出处理。
- REST Storage API对的请求作相应的处理。
- 将处理的结果存入高可用键值存储系统Etcd中。
- 在API Server响应Kubecfg的请求后,Scheduler会根据Kubernetes Client获取集群中运行Pod及Minion/Node信息。
- 依据从Kubernetes Client获取的信息,Scheduler将未分发的Pod分发到可用的Minion/Node节点上。
2.1.1 API Server[资源操作入口]
-
提供了资源对象的唯一操作入口,其他所有组件都必须通过它提供的API来操作资源数据,只有API Server与存储通信,其他模块通过API Server访问集群状态。
第一,是为了保证集群状态访问的安全。
第二,是为了隔离集群状态访问的方式和后端存储实现的方式:API Server是状态访问的方式,不会因为后端存储技术etcd的改变而改变。
-
作为kubernetes系统的入口,封装了核心对象的增删改查操作,以RESTFul接口方式提供给外部客户和内部组件调用。对相关的资源数据“全量查询”+“变化监听”,实时完成相关的业务功能。
更多API Server信息请参考:Kubernetes核心原理(一)之API Server
2.1.2 Controller Manager[内部管理控制中心]
- 实现集群故障检测和恢复的自动化工作,负责执行各种控制器,主要有:
- endpoint-controller:定期关联service和pod(关联信息由endpoint对象维护),保证service到pod的映射总是最新的。
- replication-controller:定期关联replicationController和pod,保证replicationController定义的复制数量与实际运行pod的数量总是一致的。
更多Controller Manager信息请参考:Kubernetes核心原理(二)之Controller Manager
2.1.3 Scheduler[集群分发调度器]
- Scheduler收集和分析当前Kubernetes集群中所有Minion/Node节点的资源(内存、CPU)负载情况,然后依此分发新建的Pod到Kubernetes集群中可用的节点。
- 实时监测Kubernetes集群中未分发和已分发的所有运行的Pod。
- Scheduler也监测Minion/Node节点信息,由于会频繁查找Minion/Node节点,Scheduler会缓存一份最新的信息在本地。
- 最后,Scheduler在分发Pod到指定的Minion/Node节点后,会把Pod相关的信息Binding写回API Server。
更多Scheduler信息请参考:Kubernetes核心原理(三)之Scheduler
2.2 kube-node[服务节点]
kubelet结构图
2.2.1 Kubelet[节点上的Pod管家]
-
负责Node节点上pod的创建、修改、监控、删除等全生命周期的管理
-
定时上报本Node的状态信息给API Server。
-
kubelet是Master API Server和Minion/Node之间的桥梁,接收Master API Server分配给它的commands和work,通过kube-apiserver间接与Etcd集群交互,读取配置信息。
-
具体的工作如下:
-
设置容器的环境变量、给容器绑定Volume、给容器绑定Port、根据指定的Pod运行一个单一容器、给指定的Pod创建network 容器。
-
同步Pod的状态、同步Pod的状态、从cAdvisor获取container info、 pod info、 root info、 machine info。
-
在容器中运行命令、杀死容器、删除Pod的所有容器。
-
更多Kubelet信息请参考:Kubernetes核心原理(四)之Kubelet
2.2.2 Proxy[负载均衡、路由转发]
- Proxy是为了解决外部网络能够访问跨机器集群中容器提供的应用服务而设计的,运行在每个Minion/Node上。Proxy提供TCP/UDP sockets的proxy,每创建一种Service,Proxy主要从etcd获取Services和Endpoints的配置信息(也可以从file获取),然后根据配置信息在Minion/Node上启动一个Proxy的进程并监听相应的服务端口,当外部请求发生时,Proxy会根据Load Balancer将请求分发到后端正确的容器处理。
- Proxy不但解决了同一主宿机相同服务端口冲突的问题,还提供了Service转发服务端口对外提供服务的能力,Proxy后端使用了随机、轮循负载均衡算法。
2.2.3 kubectl[集群管理命令行工具集]
- 通过客户端的kubectl命令集操作,API Server响应对应的命令结果,从而达到对kubernetes集群的管理。
参考文章:
https://yq.aliyun.com/articles/47308?spm=5176.100240.searchblog.19.jF7FFa
3.1.2 - 基于Docker及Kubernetes技术构建容器云(PaaS)平台
[编者的话]
目前很多的容器云平台通过Docker及Kubernetes等技术提供应用运行平台,从而实现运维自动化,快速部署应用、弹性伸缩和动态调整应用环境资源,提高研发运营效率。
从宏观到微观(从抽象到具体)的思路来理解:云计算→PaaS→ App Engine→XAE[XXX App Engine] (XAE泛指一类应用运行平台,例如GAE、SAE、BAE等)。
本文简要介绍了与容器云相关的几个重要概念:PaaS、App Engine、Dokcer、Kubernetes。
1. PaaS概述
1.1. PaaS概念
- PaaS(Platform as a service),平台即服务,指将软件研发的平台(或业务基础平台)作为一种服务,以SaaS的模式提交给用户。
- PaaS是云计算服务的其中一种模式,云计算是一种按使用量付费的模式的服务,类似一种租赁服务,服务可以是基础设施计算资源(IaaS),平台(PaaS),软件(SaaS)。租用IT资源的方式来实现业务需要,如同水力、电力资源一样,计算、存储、网络将成为企业IT运行的一种被使用的资源,无需自己建设,可按需获得。
- PaaS的实质是将互联网的资源服务化为可编程接口,为第三方开发者提供有商业价值的资源和服务平台。简而言之,IaaS就是卖硬件及计算资源,PaaS就是卖开发、运行环境,SaaS就是卖软件。
1.2. IaaS/PaaS/SaaS说明
| 类型 | 说明 | 比喻 | 例子 |
|---|---|---|---|
| IaaS:Infrastructure-as-a-Service(基础设施即服务) | 提供的服务是计算基础设施 | 地皮,需要自己盖房子 | Amazon EC2(亚马逊弹性云计算) |
| PaaS: Platform-as-a-Service(平台即服务) | 提供的服务是软件研发的平台或业务基础平台 | 商品房,需要自己装修 | GAE(谷歌开发者平台) |
| SaaS: Software-as-a-Service(软件即服务) | 提供的服务是运行在云计算基础设施上的应用程序 | 酒店套房,可以直接入住 | 谷歌的Gmail邮箱 |
1.3. PaaS的特点(三种层次)
| 特点 | 说明 |
|---|---|
| 平台即服务 | PaaS提供的服务就是个基础平台,一个环境,而不是具体的应用 |
| 平台及服务 | 不仅提供平台,还提供对该平台的技术支持、优化等服务 |
| 平台级服务 | “平台级服务”即强大稳定的平台和专业的技术支持团队,保障应用的稳定使用 |
2. App Engine概述
2.1. App Engine概念
App Engine是PaaS模式的一种实现方式,App Engine将应用运行所需的 IT 资源和基础设施以服务的方式提供给用户,包括了中间件服务、资源管理服务、弹性调度服务、消息服务等多种服务形式。App Engine的目标是对应用提供完整生命周期(包括设计、开发、测试和部署等阶段)的支持,从而减少了用户在购置和管理应用生命周期内所必须的软硬件以及部署应用和IT 基础设施的成本,同时简化了以上工作的复杂度。常见的App Engine有:GAE(Google App Engine),SAE(Sina App Engine),BAE(Baidu App Engine)。
App Engine利用虚拟化与自动化技术实现快速搭建部署应用运行环境和动态调整应用运行时环境资源这两个目标。一方面实现即时部署以及快速回收,降低了环境搭建时间,避免了手工配置错误,快速重复搭建环境,及时回收资源, 减少了低利用率硬件资源的空置。另一方面,根据应用运行时的需求对应用环境进行动态调整,实现了应用平台的弹性扩展和自优化,减少了非高峰时硬件资源的空置。
简而言之,App Engine主要目标是:Easy to maintain(维护), Easy to scale(扩容), Easy to build(构建)。
2.2. 架构设计
2.3. 组成模块说明
| 组成模块 | 模块说明 |
|---|---|
| App Router[流量接入层] | 接收用户请求,并转发到不同的App Runtime。 |
| App Runtime[应用运行层] | 应用运行环境,为各个应用提供基本的运行引擎,从而让app能够运行起来。 |
| Services[基础服务层] | 各个通用基础服务,主要是对主流的服务提供通用的接入,例如数据库等。 |
| Platform Control[平台控制层] | 整个平台的控制中心,实现业务调度,弹性扩容、资源审计、集群管理等相关工作。 |
| Manage System[管理界面层] | 提供友好可用的管理操作界面方便平台管理员来控制管理整个平台。 |
| Platform Support[平台支持层] | 为应用提供相关的支持,比如应用监控、问题定位、分布式日志重建、统计分析等。 |
| Log Center[日志中心] | 实时收集相关应用及系统的日志(日志收集),提供实时计算和分析平台(日志处理)。 |
| Code Center[代码中心] | 完成代码存储、部署上线相关的工作。 |
3. 容器云平台技术栈
| 功能组成部分 | 使用工具 |
|---|---|
| 应用载体 | Docker |
| 编排工具 | Kubernetes |
| 配置数据 | Etcd |
| 网络管理 | Flannel |
| 存储管理 | Ceph |
| 底层实现 | Linux内核的Namespace[资源隔离]和CGroups[资源控制] |
- Namespace[资源隔离] Namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的,而是属于某个特定的Namespace。每个namespace下的资源对于其他namespace下的资源都是透明,不可见的。
- CGroups[资源控制] CGroup(control group)是将任意进程进行分组化管理的Linux内核功能。CGroup本身是提供将进程进行分组化管理的功能和接口的基础结构,I/O或内存的分配控制等具体的资源管理功能是通过这个功能来实现的。CGroups可以限制、记录、隔离进程组所使用的物理资源(包括:CPU、memory、IO等),为容器实现虚拟化提供了基本保证。CGroups本质是内核附加在程序上的一系列钩子(hooks),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。
4. Docker概述
更多详情请参考:Docker整体架构图
4.1. Docker介绍
- Docker - Build, Ship, and Run Any App, Anywhere
- Docker是一种Linux容器工具集,它是为“构建(Build)、交付(Ship)和运行(Run)”分布式应用而设计的。
- Docker相当于把应用以及应用所依赖的环境完完整整地打成了一个包,这个包拿到哪里都能原生运行。因此可以在开发、测试、运维中保证环境的一致性。
- Docker的本质:Docker=LXC(Namespace+CGroups)+Docker Images,即在Linux内核的Namespace[资源隔离]和CGroups[资源控制]技术的基础上通过镜像管理机制来实现轻量化设计。
4.2. Docker的基本概念
4.2.1. 镜像
Docker 镜像就是一个只读的模板,可以把镜像理解成一个模子(模具),由模子(镜像)制作的成品(容器)都是一样的(除非在生成时加额外参数),修改成品(容器)本身并不会对模子(镜像)产生影响(除非将成品提交成一个模子),容器重启时,即由模子(镜像)重新制作成一个成品(容器),与其他由该模子制作成的成品并无区别。
例如:一个镜像可以包含一个完整的 ubuntu 操作系统环境,里面仅安装了 Apache 或用户需要的其它应用程序。镜像可以用来创建 Docker 容器。Docker 提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户可以直接从其他人那里下载一个已经做好的镜像来直接使用。
4.2.2. 容器
Docker 利用容器来运行应用。容器是从镜像创建的运行实例。它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
4.2.3. 仓库
仓库是集中存放镜像文件的场所。有时候会把仓库和仓库注册服务器(Registry)混为一谈,并不严格区分。实际上,仓库注册服务器上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。
4.3. Docker的优势
-
容器的快速轻量
容器的启动,停止和销毁都是以秒或毫秒为单位的,并且相比传统的虚拟化技术,使用容器在CPU、内存,网络IO等资源上的性能损耗都有同样水平甚至更优的表现。
-
一次构建,到处运行
当将容器固化成镜像后,就可以非常快速地加载到任何环境中部署运行。而构建出来的镜像打包了应用运行所需的程序、依赖和运行环境, 这是一个完整可用的应用集装箱,在任何环境下都能保证环境一致性。
-
完整的生态链
容器技术并不是Docker首创,但是以往的容器实现只关注于如何运行,而Docker站在巨人的肩膀上进行整合和创新,特别是Docker镜像的设计,完美地解决了容器从构建、交付到运行,提供了完整的生态链支持。
5. Kubernetes概述
更多详情请参考:Kubernetes总架构图
5.1. Kubernetes介绍
Kubernetes是Google开源的容器集群管理系统。它构建Docker技术之上,为容器化的应用提供资源调度、部署运行、服务发现、扩容缩容等整一套功能,本质上可看作是基于容器技术的Micro-PaaS平台,即第三代PaaS的代表性项目。
5.2. Kubernetes的基本概念
5.2.1. Pod
Pod是若干个相关容器的组合,是一个逻辑概念,Pod包含的容器运行在同一个宿主机上,这些容器使用相同的网络命名空间、IP地址和端口,相互之间能通过localhost来发现和通信,共享一块存储卷空间。在Kubernetes中创建、调度和管理的最小单位是Pod。一个Pod一般只放一个业务容器和一个用于统一网络管理的网络容器。
5.2.2. Replication Controller
Replication Controller是用来控制管理Pod副本(Replica,或者称实例),Replication Controller确保任何时候Kubernetes集群中有指定数量的Pod副本在运行,如果少于指定数量的Pod副本,Replication Controller会启动新的Pod副本,反之会杀死多余的以保证数量不变。另外Replication Controller是弹性伸缩、滚动升级的实现核心。
5.2.3. Service
Service是真实应用服务的抽象,定义了Pod的逻辑集合和访问这个Pod集合的策略,Service将代理Pod对外表现为一个单一访问接口,外部不需要了解后端Pod如何运行,这给扩展或维护带来很大的好处,提供了一套简化的服务代理和发现机制。
5.2.4. Label
Label是用于区分Pod、Service、Replication Controller的Key/Value键值对,实际上Kubernetes中的任意API对象都可以通过Label进行标识。每个API对象可以有多个Label,但是每个Label的Key只能对应一个Value。Label是Service和Replication Controller运行的基础,它们都通过Label来关联Pod,相比于强绑定模型,这是一种非常好的松耦合关系。
5.2.5. Node
Kubernets属于主从的分布式集群架构,Kubernets Node(简称为Node,早期版本叫做Minion)运行并管理容器。Node作为Kubernetes的操作单元,将用来分配给Pod(或者说容器)进行绑定,Pod最终运行在Node上,Node可以认为是Pod的宿主机。
5.3. Kubernetes架构
3.2 - kubernetes对象
3.2.1 - Kubernetes基本概念
1. Master
集群的控制节点,负责整个集群的管理和控制,kubernetes的所有的命令基本都是发给Master,由它来负责具体的执行过程。
1.1. Master的组件
- kube-apiserver:资源增删改查的入口
- kube-controller-manager:资源对象的大总管
- kube-scheduler:负责资源调度(Pod调度)
- etcd Server:kubernetes的所有的资源对象的数据保存在etcd中。
2. Node
Node是集群的工作负载节点,默认情况kubelet会向Master注册自己,一旦Node被纳入集群管理范围,kubelet会定时向Master汇报自身的情报,包括操作系统,Docker版本,机器资源情况等。
如果Node超过指定时间不上报信息,会被Master判断为“失联”,标记为Not Ready,随后Master会触发Pod转移。
2.1. Node的组件
- kubelet:Pod的管家,与Master通信
- kube-proxy:实现kubernetes Service的通信与负载均衡机制的重要组件
- Docker:容器的创建和管理
2.2. Node相关命令
kubectl get nodes
kuebctl describe node {node_name}
2.3. describe命令的Node信息
- Node基本信息:名称、标签、创建时间等
- Node当前的状态,Node启动后会进行自检工作,磁盘是否满,内存是否不足,若都正常则切换为Ready状态。
- Node的主机地址与主机名
- Node上的资源总量:CPU,内存,最大可调度Pod数量等
- Node可分配资源量:当前Node可用于分配的资源量
- 主机系统信息:主机唯一标识符UUID,Linux kernel版本号,操作系统,kubernetes版本,kubelet与kube-proxy版本
- 当前正在运行的Pod列表及概要信息
- 已分配的资源使用概要,例如资源申请的最低、最大允许使用量占系统总量的百分比
- Node相关的Event信息。
3. Pod
Pod是Kubernetes中操作的基本单元。每个Pod中有个根容器(Pause容器),Pause容器的状态代表整个容器组的状态,其他业务容器共享Pause的IP,即Pod IP,共享Pause挂载的Volume,这样简化了同个Pod中不同容器之间的网络问题和文件共享问题。

- Kubernetes集群中,同宿主机的或不同宿主机的Pod之间要求能够TCP/IP直接通信,因此采用虚拟二层网络技术来实现,例如Flannel,Openvswitch(OVS)等,这样在同个集群中,不同的宿主机的Pod IP为不同IP段的IP,集群中的所有Pod IP都是唯一的,不同Pod之间可以直接通信。
- Pod有两种类型:普通Pod和静态Pod。静态Pod即不通过K8S调度和创建,直接在某个具体的Node机器上通过具体的文件来启动。普通Pod则是由K8S创建、调度,同时数据存放在ETCD中。
- Pod IP和具体的容器端口(ContainnerPort)组成一个具体的通信地址,即Endpoint。一个Pod中可以存在多个容器,可以有多个端口,Pod IP一样,即有多个Endpoint。
- Pod Volume是定义在Pod之上,被各个容器挂载到自己的文件系统中,可以用分布式文件系统实现后端存储功能。
- Pod中的Event事件可以用来排查问题,可以通过kubectl describe pod xxx 来查看对应的事件。
- 每个Pod可以对其能使用的服务器上的计算资源设置限额,一般为CPU和Memory。K8S中一般将千分之一个的CPU配置作为最小单位,用m表示,是一个绝对值,即100m对于一个Core的机器还是48个Core的机器都是一样的大小。Memory配额也是个绝对值,单位为内存字节数。
- 资源配额的两个参数
- Requests:该资源的最小申请量,系统必须满足要求。
- Limits:该资源最大允许使用量,当超过该量,K8S会kill并重启Pod。

4. Label
- Label是一个键值对,可以附加在任何对象上,比如Node,Pod,Service,RC等。Label和资源对象是多对多的关系,即一个Label可以被添加到多个对象上,一个对象也可以定义多个Label。
- Label的作用主要用来实现精细的、多维度的资源分组管理,以便进行资源分配,调度,配置,部署等工作。
- Label通俗理解就是“标签”,通过标签来过滤筛选指定的对象,进行具体的操作。k8s通过Label Selector(标签选择器)来筛选指定Label的资源对象,类似SQL语句中的条件查询(WHERE语句)。
- Label Selector有基于等式和基于集合的两种表达方式,可以多个条件进行组合使用。
- 基于等式:name=redis-slave(匹配name=redis-slave的资源对象);env!=product(匹配所有不具有标签env=product的资源对象)
- 基于集合:name in (redis-slave,redis-master);name not in (php-frontend)(匹配所有不具有标签name=php-frontend的资源对象)
使用场景
- kube-controller进程通过资源对象RC上定义的Label Selector来筛选要监控的Pod副本数,从而实现副本数始终保持预期数目。
- kube-proxy进程通过Service的Label Selector来选择对应Pod,自动建立每个Service到对应Pod的请求转发路由表,从而实现Service的智能负载均衡机制。
- kube-scheduler实现Pod定向调度:对Node定义特定的Label,并且在Pod定义文件中使用NodeSelector标签调度策略。
5. Replication Controller(RC)
RC是k8s系统中的核心概念,定义了一个期望的场景。
主要包括:
- Pod期望的副本数(replicas)
- 用于筛选目标Pod的Label Selector
- 用于创建Pod的模板(template)
RC特性说明:
- Pod的缩放可以通过以下命令实现:kubectl scale rc redis-slave --replicas=3
- 删除RC并不会删除该RC创建的Pod,可以将副本数设置为0,即可删除对应Pod。或者通过kubectl stop /delete命令来一次性删除RC和其创建的Pod。
- 改变RC中Pod模板的镜像版本可以实现滚动升级(Rolling Update)。具体操作见https://kubernetes.io/docs/tasks/run-application/rolling-update-replication-controller/
- Kubernetes1.2以上版本将RC升级为Replica Set,它与当前RC的唯一区别在于Replica Set支持基于集合的Label Selector(Set-based selector),而旧版本RC只支持基于等式的Label Selector(equality-based selector)。
- Kubernetes1.2以上版本通过Deployment来维护Replica Set而不是单独使用Replica Set。即控制流为:Delpoyment→Replica Set→Pod。即新版本的Deployment+Replica Set替代了RC的作用。
6. Deployment
Deployment是kubernetes 1.2引入的概念,用来解决Pod的编排问题。Deployment可以理解为RC的升级版(RC+Reolicat Set)。特点在于可以随时知道Pod的部署进度,即对Pod的创建、调度、绑定节点、启动容器完整过程的进度展示。
使用场景
- 创建一个Deployment对象来生成对应的Replica Set并完成Pod副本的创建过程。
- 检查Deployment的状态来确认部署动作是否完成(Pod副本的数量是否达到预期值)。
- 更新Deployment以创建新的Pod(例如镜像升级的场景)。
- 如果当前Deployment不稳定,回退到上一个Deployment版本。
- 挂起或恢复一个Deployment。
可以通过kubectl describe deployment来查看Deployment控制的Pod的水平拓展过程。
7. Horizontal Pod Autoscaler(HPA)
Horizontal Pod Autoscaler(HPA)即Pod横向自动扩容,与RC一样也属于k8s的资源对象。
HPA原理:通过追踪分析RC控制的所有目标Pod的负载变化情况,来确定是否针对性调整Pod的副本数。
Pod负载度量指标:
- CPUUtilizationPercentage:Pod所有副本自身的CPU利用率的平均值。即当前Pod的CPU使用量除以Pod Request的值。
- 应用自定义的度量指标,比如服务每秒内响应的请求数(TPS/QPS)。
8. Service(服务)
8.1. Service概述

Service定义了一个服务的访问入口地址,前端应用通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端的Pod副本集群之间是通过Label Selector来实现“无缝对接”。RC保证Service的Pod副本实例数目保持预期水平。
8.2. kubernetes的服务发现机制
主要通过kube-dns这个组件来进行DNS方式的服务发现。
8.3. 外部系统访问Service的问题
| IP类型 | 说明 |
|---|---|
| Node IP | Node节点的IP地址 |
| Pod IP | Pod的IP地址 |
| Cluster IP | Service的IP地址 |
8.3.1. Node IP
NodeIP是集群中每个节点的物理网卡IP地址,是真实存在的物理网络,kubernetes集群之外的节点访问kubernetes内的某个节点或TCP/IP服务的时候,需要通过NodeIP进行通信。
8.3.2. Pod IP
Pod IP是每个Pod的IP地址,是Docker Engine根据docker0网桥的IP段地址进行分配的,是一个虚拟二层网络,集群中一个Pod的容器访问另一个Pod中的容器,是通过Pod IP进行通信的,而真实的TCP/IP流量是通过Node IP所在的网卡流出的。
8.3.3. Cluster IP
- Service的Cluster IP是一个虚拟IP,只作用于Service这个对象,由kubernetes管理和分配IP地址(来源于Cluster IP地址池)。
- Cluster IP无法被ping通,因为没有一个实体网络对象来响应。
- Cluster IP结合Service Port组成的具体通信端口才具备TCP/IP通信基础,属于kubernetes集群内,集群外访问该IP和端口需要额外处理。
- k8s集群内Node IP 、Pod IP、Cluster IP之间的通信采取k8s自己的特殊的路由规则,与传统IP路由不同。
8.3.4. 外部访问Kubernetes集群
通过宿主机与容器端口映射的方式进行访问,例如:Service定位文件如下:
可以通过任意Node的IP 加端口访问该服务。也可以通过Nginx或HAProxy来设置负载均衡。
9. Volume(存储卷)
9.1. Volume的功能
- Volume是Pod中能够被多个容器访问的共享目录,可以让容器的数据写到宿主机上或者写文件到网络存储中
- 可以实现容器配置文件集中化定义与管理,通过ConfigMap资源对象来实现。
9.2. Volume的特点
k8s中的Volume与Docker的Volume相似,但不完全相同。
- k8s上Volume定义在Pod上,然后被一个Pod中的多个容器挂载到具体的文件目录下。
- k8s的Volume与Pod生命周期相关而不是容器是生命周期,即容器挂掉,数据不会丢失但是Pod挂掉,数据则会丢失。
- k8s中的Volume支持多种类型的Volume:Ceph、GlusterFS等分布式系统。
9.3. Volume的使用方式
先在Pod上声明一个Volume,然后容器引用该Volume并Mount到容器的某个目录。
9.4. Volume类型
9.4.1. emptyDir
emptyDir Volume是在Pod分配到Node时创建的,初始内容为空,无须指定宿主机上对应的目录文件,由K8S自动分配一个目录,当Pod被删除时,对应的emptyDir数据也会永久删除。
作用:
- 临时空间,例如程序的临时文件,无须永久保留
- 长时间任务的中间过程CheckPoint的临时保存目录
- 一个容器需要从另一个容器中获取数据的目录(即多容器共享目录)
说明:
目前用户无法设置emptyVolume的使用介质,如果kubelet的配置使用硬盘则emptyDir将创建在该硬盘上。
9.4.2. hostPath
hostPath是在Pod上挂载宿主机上的文件或目录。
作用:
- 容器应用日志需要持久化时,可以使用宿主机的高速文件系统进行存储
- 需要访问宿主机上Docker引擎内部数据结构的容器应用时,可以通过定义hostPath为宿主机/var/lib/docker目录,使容器内部应用可以直接访问Docker的文件系统。
注意点:
- 在不同的Node上具有相同配置的Pod可能会因为宿主机上的目录或文件不同导致对Volume上目录或文件的访问结果不一致。
- 如果使用了资源配额管理,则kubernetes无法将hostPath在宿主机上使用的资源纳入管理。
9.4.3. gcePersistentDisk
表示使用谷歌公有云提供的永久磁盘(Persistent Disk ,PD)存放Volume的数据,它与EmptyDir不同,PD上的内容会被永久保存。当Pod被删除时,PD只是被卸载时,但不会被删除。需要先创建一个永久磁盘,才能使用gcePersistentDisk。
使用gcePersistentDisk的限制条件:
- Node(运行kubelet的节点)需要是GCE虚拟机。
- 虚拟机需要与PD存在于相同的GCE项目中和Zone中。
10. Persistent Volume
Volume定义在Pod上,属于“计算资源”的一部分,而Persistent Volume和Persistent Volume Claim是网络存储,简称PV和PVC,可以理解为k8s集群中某个网络存储中对应的一块存储。
- PV是网络存储,不属于任何Node,但可以在每个Node上访问。
- PV不是定义在Pod上,而是独立于Pod之外定义。
- PV常见类型:GCE Persistent Disks、NFS、RBD等。
PV是有状态的对象,状态类型如下:
- Available:空闲状态
- Bound:已经绑定到某个PVC上
- Released:对应的PVC已经删除,但资源还没有回收
- Failed:PV自动回收失败
11. Namespace
Namespace即命名空间,主要用于多租户的资源隔离,通过将资源对象分配到不同的Namespace上,便于不同的分组在共享资源的同时可以被分别管理。
k8s集群启动后会默认创建一个“default”的Namespace。可以通过kubectl get namespaecs查看。
可以通过kubectl config use-context namespace配置当前k8s客户端的环境,通过kubectl get pods获取当前namespace的Pod。或者通过kubectl get pods --namespace=NAMESPACE来获取指定namespace的Pod。
Namespace yaml文件的定义
12. Annotation(注解)
Annotation与Label类似,也使用key/value的形式进行定义,Label定义元数据(Metadata),Annotation定义“附加”信息。
通常Annotation记录信息如下:
- build信息,release信息,Docker镜像信息等。
- 日志库、监控库等。
参考《Kubernetes权威指南》
3.2.2 - 理解kubernetes对象
1. kubernetes对象概述
kubernetes中的对象是一些持久化的实体,可以理解为是对集群状态的描述或期望。
包括:
- 集群中哪些node上运行了哪些容器化应用
- 应用的资源是否满足使用
- 应用的执行策略,例如重启策略、更新策略、容错策略等。
kubernetes的对象是一种意图(期望)的记录,kubernetes会始终保持预期创建的对象存在和集群运行在预期的状态下。
操作kubernetes对象(增删改查)需要通过kubernetes API,一般有以下几种方式:
kubectl命令工具Client library的方式,例如 client-go
2. Spec and Status
每个kubernetes对象的结构描述都包含spec和status两个部分。
spec:该内容由用户提供,描述用户期望的对象特征及集群状态。status:该内容由kubernetes集群提供和更新,描述kubernetes对象的实时状态。
任何时候,kubernetes都会控制集群的实时状态status与用户的预期状态spec一致。
例如:当你定义Deployment的描述文件,指定集群中运行3个实例,那么kubernetes会始终保持集群中运行3个实例,如果任何实例挂掉,kubernetes会自动重建新的实例来保持集群中始终运行用户预期的3个实例。
3. 对象描述文件
当你要创建一个kubernetes对象的时候,需要提供该对象的描述信息spec,来描述你的对象在kubernetes中的预期状态。
一般使用kubernetes API来创建kubernetes对象,其中spec信息可以以JSON的形式存放在request body中,也可以以.yaml文件的形式通过kubectl工具创建。
例如,以下为Deployment对象对应的yaml文件:
apiVersion: apps/v1beta2 # for versions before 1.8.0 use apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
执行kubectl create的命令
#create command
kubectl create -f https://k8s.io/docs/user-guide/nginx-deployment.yaml --record
#output
deployment "nginx-deployment" created
4. 必须字段
在对象描述文件.yaml中,必须包含以下字段。
- apiVersion:kubernetes API的版本
- kind:kubernetes对象的类型
- metadata:唯一标识该对象的元数据,包括
name,UID,可选的namespace - spec:标识对象的详细信息,不同对象的
spec的格式不同,可以嵌套其他对象的字段。
文章参考:
https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/
3.3 - Pod对象
3.3.1 - Pod介绍
1. Pod是什么(what)
1.1. Pod概念
- Pod是kubernetes集群中最小的部署和管理的
基本单元,协同寻址,协同调度。 - Pod是一个或多个容器的集合,是一个或一组服务(进程)的抽象集合。
- Pod中可以共享网络和存储(可以简单理解为一个逻辑上的虚拟机,但并不是虚拟机)。
- Pod被创建后用一个
UID来唯一标识,当Pod生命周期结束,被一个等价Pod替代,UID将重新生成。
1.1.1. Pod与Docker
- Docker是目前Pod最常用的容器环境,但仍支持其他容器环境。
- Pod是一组被模块化的拥有共享命名空间和共享存储卷的容器,但并没有共享PID 命名空间(即同个Pod的不同容器中进程的PID是独立的,互相看不到非自己容器的进程)。
1.1.2. Pod中容器的运行方式
- 只运行一个单独的容器
即one-container-per-Pod模式,是最常用的模式,可以把这样的Pod看成单独的一个容器去管理。
- 运行多个强关联的容器
即sidecar模式,Pod 封装了一组紧耦合、共享资源、协同寻址的容器,将这组容器作为一个管理单元。
1.2. Pod管理多个容器
Pod是一组紧耦合的容器的集合,Pod内的容器作为一个整体以Pod形式进行协同寻址,协同调度、协同管理。相同Pod内的容器共享网络和存储。
1.2.1. 网络
- 每个Pod被分配了唯一的IP地址,该Pod内的所有容器共享一个网络空间,包括IP和端口。
- 同个Pod不同容器之间通过
localhost通信,Pod内端口不能冲突。 - 不同Pod之间的通信则通过IP+端口的形式来访问到Pod内的具体服务(容器)。
1.2.2. 存储
- 可以在Pod中创建共享
存储卷的方式来实现不同容器之间数据共享。
2. 为什么需要Pod(why)
2.1. 管理需求
Pod 是一种模式的抽象:互相协作的多个进程(容器)共同形成一个完整的服务。以一个或多个容器的方式组合成一个整体,作为管理的基本单元,通过Pod可以方便部署、水平扩展,协同调度等。
2.2. 资源共享和通信
Pod作为多个紧耦合的容器的集合,通过共享网络和存储的方式来简化紧耦合容器之间的通信,从这个角度,可以将Pod简单理解为一个逻辑上的“虚拟机”。而不同的Pod之间的通信则通过Pod的IP和端口的方式。
2.3. Pod设计的优势
- 调度器和控制器的可拔插性。
- 将Pod 的生存期从 controller 中剥离出来,从而减少相互影响。
- 高可用--在终止和删除 Pod 前,需要提前生成替代 Pod。
- 集群级别的功能和 Kubelet(Pod Controller) 级别的功能组合更加清晰。
3. Pod的使用(how)
Pod一般是通过各种不同类型的Controller对Pod进行管理和控制,包括自我恢复(例如Pod因异常退出,则会再起一个相同的Pod替代该Pod,而该Pod则会被清除)。也可以不通过Controller单独创建一个Pod,但一般很少这么操作,因为这个Pod是一个孤立的实体,并不会被Controller管理。
3.1. Controller
Controller是kubernetes中用于对Pod进行管理的控制器,通过该控制器让Pod始终维持在一个用户原本设定或期望的状态。如果节点宕机或者Pod因其他原因死亡,则会在其他节点起一个相同的Pod来替代该Pod。
常用的Controller有:
DeploymentStatefulSetDaemonSet
Controller是通过用户提供的Pod模板来创建和控制Pod。
3.2. Pod模板
Pod模板用来定义Pod的各种属性,Controller通过Pod模板来生成对应的Pod。
Pod模板类似一个饼干模具,通过模具已经生成的饼干与原模具已经没有关系,即对原模具的修改不会影响已经生成的饼干,只会对通过修改后的模具生成的饼干有影响。这种方式可以更加方便地控制和管理Pod。
4. Pod的终止
用户发起一个删除Pod的请求,系统会先发送TERM信号给每个容器的主进程,如果在宽限期(默认30秒)主进程没有自主终止运行,则系统会发送KILL信号给该进程,接着Pod将被删除。
4.1. Pod终止的流程
- 用户发送一个删除 Pod 的命令, 并使用默认的宽限期(30s)。
- 把 API server 上的 pod 的时间更新成 Pod 与宽限期一起被认为 “dead” 之外的时间点。
- 使用客户端的命令,显示出的Pod的状态为
terminating。 - (与第3步同时发生)Kubelet 发现某一个 Pod 由于时间超过第2步的设置而被标志成 terminating 状态时, Kubelet 将启动一个停止进程。
- 如果 pod 已经被定义成一个
preStop hook,这会在 pod 内部进行调用。如果宽限期已经过期但 preStop 锚依然还在运行,将调用第2步并在原来的宽限期上加一个小的时间窗口(2 秒钟)。 - 把 Pod 里的进程发送到
TERM信号。
- 如果 pod 已经被定义成一个
- (与第3步同时发生),Pod 被从终端的服务列表里移除,同时也不再被 replication controllers 看做时一组运行中的 pods。 在负载均衡(比如说 service proxy)会将它们从轮询中移除前, Pods 这种慢关闭的方式可以继续为流量提供服务。
- 当宽期限过期时, 任何还在 Pod 里运行的进程都会被
SIGKILL杀掉。 - Kubelet 通过在 API server 把宽期限设置成0(立刻删除)的方式完成删除 Pod的过程。 这时 Pod 在 API 里消失,也不再能被用户看到。
4.2. 强制删除Pod
强制删除Pod是指从k8s集群状态和Etcd中立刻删除对应的Pod数据,API Server不会等待kubelet的确认信息。被强制删除后,即可重新创建一个相同名字的Pod。
删除默认的宽限期是30秒,通过将宽限期设置为0的方式可以强制删除Pod。
通过kubectl delete 命令后加--force和--grace-period=0的参数强制删除Pod。
kubectl delete pod <pod_name> --namespace=<namespace> --force --grace-period=0
4.3. Pod特权模式
特权模式是指让Pod中的进程具有访问宿主机系统设备或使用网络栈操作等的能力,例如编写网络插件和卷插件。
通过将container spec中的SecurityContext设置为privileged即将该容器赋予了特权模式。特权模式的使用要求k8s版本高于v1.1。
参考文章:
3.3.2 - Pod定义文件
1. Pod的基本用法
1.1. 说明
- Pod实际上是容器的集合,在k8s中对运行容器的要求为:容器的主程序需要一直在前台运行,而不是后台运行。应用可以改造成前台运行的方式,例如Go语言的程序,直接运行二进制文件;java语言则运行主类;tomcat程序可以写个运行脚本。或者通过supervisor的进程管理工具,即supervisor在前台运行,应用程序由supervisor管理在后台运行。具体可参考supervisord。
- 当多个应用之间是紧耦合的关系时,可以将多个应用一起放在一个Pod中,同个Pod中的多个容器之间互相访问可以通过localhost来通信(可以把Pod理解成一个虚拟机,共享网络和存储卷)。
1.2. Pod相关命令
| 操作 | 命令 | 说明 |
|---|---|---|
| 创建 | kubectl create -f frontend-localredis-pod.yaml | |
| 查询Pod运行状态 | kubectl get pods --namespace=<NAMESPACE> |
|
| 查询Pod详情 | kebectl describe pod <POD_NAME> --namespace=<NAMESPACE> |
该命令常用来排查问题,查看Event事件 |
| 删除 | kubectl delete pod <POD_NAME> ;kubectl delete pod --all |
|
| 更新 | kubectl replace pod.yaml | - |
2. Pod的定义文件
apiVersion: v1
kind: Pod
metadata:
name: string
namaspace: string
labels:
- name: string
annotations:
- name: string
spec:
containers:
- name: string
images: string
imagePullPolice: [Always | Never | IfNotPresent]
command: [string]
args: [string]
workingDir: string
volumeMounts:
- name: string
mountPath: string
readOnly: boolean
ports:
- name: string
containerPort: int
hostPort: int
protocol: string
env:
- name: string
value: string
resources:
limits:
cpu: string
memory: string
requests:
cpu: string
memory: string
livenessProbe:
exec:
command: [string]
httpGet:
path: string
port: int
host: string
scheme: string
httpHeaders:
- name: string
value: string
tcpSocket:
port: int
initialDelaySeconds: number
timeoutSeconds: number
periodSeconds: number
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure]
nodeSelector: object
imagePullSecrets:
- name: string
hostNetwork: false
volumes:
- name: string
emptyDir: {}
hostPath:
path: string
secret:
secretName: string
items:
- key: string
path: string
configMap:
name: string
items:
- key: string
path: string
3. 静态pod
静态Pod是由kubelet进行管理,仅存在于特定Node上的Pod。它们不能通过API Server进行管理,无法与ReplicationController、Deployment或DaemonSet进行关联,并且kubelet也无法对其健康检查。
静态Pod总是由kubelet创建,并且总在kubelet所在的Node上运行。
创建静态Pod的方式:
3.1. 通过配置文件方式
需要设置kubelet的启动参数“–config”,指定kubelet需要监控的配置文件所在目录,kubelet会定期扫描该目录,并根据该目录的.yaml或.json文件进行创建操作。静态Pod无法通过API Server删除(若删除会变成pending状态),如需删除该Pod则将yaml或json文件从这个目录中删除。
例如:
配置目录为/etc/kubelet.d/,配置启动参数:--config=/etc/kubelet.d/,该目录下放入static-web.yaml。
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
name: static-web
spec:
containers:
- name: static-web
image: nginx
ports:
- name: web
containerPort: 80
参考文章
- 《Kubernetes权威指南》
3.3.3 - Pod生命周期
1. Pod phase
Pod的phase是Pod生命周期中的简单宏观描述,定义在Pod的PodStatus对象的phase 字段中。
phase有以下几种值:
| 状态值 | 说明 |
|---|---|
挂起(Pending) |
Pod 已被 Kubernetes 系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度 Pod 的时间和通过网络下载镜像的时间。 |
运行中(Running) |
该 Pod 已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态。 |
成功(Succeeded) |
Pod 中的所有容器都被成功终止,并且不会再重启。 |
失败(Failed) |
Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。 |
未知(Unknown) |
因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。 |
2. Pod 状态
Pod 有一个 PodStatus 对象,其中包含一个 PodCondition 数组。 PodCondition包含以下以下字段:
lastProbeTime:Pod condition最后一次被探测到的时间戳。lastTransitionTime:Pod最后一次状态转变的时间戳。message:状态转化的信息,一般为报错信息,例如:containers with unready status: [c-1]。reason:最后一次状态形成的原因,一般为报错原因,例如:ContainersNotReady。status:包含的值有 True、False 和 Unknown。type:Pod状态的几种类型。
其中type字段包含以下几个值:
PodScheduled:Pod已经被调度到运行节点。Ready:Pod已经可以接收请求提供服务。Initialized:所有的init container已经成功启动。Unschedulable:无法调度该Pod,例如节点资源不够。ContainersReady:Pod中的所有容器已准备就绪。
3. 重启策略
Pod通过restartPolicy字段指定重启策略,重启策略类型为:Always、OnFailure 和 Never,默认为 Always。
restartPolicy 仅指通过同一节点上的 kubelet 重新启动容器。
| 重启策略 | 说明 |
|---|---|
| Always | 当容器失效时,由kubelet自动重启该容器 |
| OnFailure | 当容器终止运行且退出码不为0时,由kubelet自动重启该容器 |
| Never | 不论容器运行状态如何,kubelet都不会重启该容器 |
说明:
可以管理Pod的控制器有Replication Controller,Job,DaemonSet,及kubelet(静态Pod)。
- RC和DaemonSet:必须设置为Always,需要保证该容器持续运行。
- Job:OnFailure或Never,确保容器执行完后不再重启。
- kubelet:在Pod失效的时候重启它,不论RestartPolicy设置为什么值,并且不会对Pod进行健康检查。
4. Pod的生命
Pod的生命周期一般通过Controler 的方式管理,每种Controller都会包含PodTemplate来指明Pod的相关属性,Controller可以自动对pod的异常状态进行重新调度和恢复,除非通过Controller的方式删除其管理的Pod,不然kubernetes始终运行用户预期状态的Pod。
控制器的分类
- 使用
Job运行预期会终止的 Pod,例如批量计算。Job 仅适用于重启策略为OnFailure或Never的 Pod。 - 对预期不会终止的 Pod 使用
ReplicationController、ReplicaSet和Deployment,例如 Web 服务器。 ReplicationController 仅适用于具有restartPolicy为 Always 的 Pod。 - 提供特定于机器的系统服务,使用
DaemonSet为每台机器运行一个 Pod 。
如果节点死亡或与集群的其余部分断开连接,则 Kubernetes 将应用一个策略将丢失节点上的所有 Pod 的 phase 设置为 Failed。
5. Pod状态转换
常见的状态转换
| Pod的容器数 | Pod当前状态 | 发生的事件 | Pod结果状态 | ||
|---|---|---|---|---|---|
| RestartPolicy=Always | RestartPolicy=OnFailure | RestartPolicy=Never | |||
| 包含一个容器 | Running | 容器成功退出 | Running | Succeeded | Succeeded |
| 包含一个容器 | Running | 容器失败退出 | Running | Running | Failure |
| 包含两个容器 | Running | 1个容器失败退出 | Running | Running | Running |
| 包含两个容器 | Running | 容器被OOM杀掉 | Running | Running | Failure |
5.1. 容器运行时内存超出限制
- 容器以失败状态终止。
- 记录 OOM 事件。
- 如果
restartPolicy为:- Always:重启容器;Pod
phase仍为 Running。 - OnFailure:重启容器;Pod
phase仍为 Running。 - Never: 记录失败事件;Pod
phase仍为 Failed。
- Always:重启容器;Pod
5.2. 磁盘故障
- 杀掉所有容器。
- 记录适当事件。
- Pod
phase变成 Failed。 - 如果使用控制器来运行,Pod 将在别处重建。
5.3. 运行节点挂掉
- 节点控制器等待直到超时。
- 节点控制器将 Pod
phase设置为 Failed。 - 如果是用控制器来运行,Pod 将在别处重建。
参考文章:
3.3.4 - Pod健康检查
Pod健康检查
Pod的健康状态由两类探针来检查:LivenessProbe和ReadinessProbe。
1. 探针类型
1. livenessProbe(存活探针)
- 表明容器是否正在运行。
- 如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其
重启策略的影响。 - 如果容器不提供存活探针,则默认状态为
Success。
2. readinessProbe(就绪探针)
- 表明容器是否可以正常接受请求。
- 如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的 IP 地址。
- 初始延迟之前的就绪状态默认为
Failure。 - 如果容器不提供就绪探针,则默认状态为
Success。
2. 探针使用场景
- 如果容器异常可以自动崩溃,则不一定要使用探针,可以由Pod的
restartPolicy执行重启操作。 存活探针适用于希望容器探测失败后被杀死并重新启动,需要指定restartPolicy为 Always 或 OnFailure。就绪探针适用于希望Pod在不能正常接收流量的时候被剔除,并且在就绪探针探测成功后才接收流量。
探针是kubelet对容器执行定期的诊断,主要通过调用容器配置的四类Handler实现:
Handler的类型:
ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。TCPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于200 且小于 400,则诊断被认为是成功的。GRPCAction:调用GRPC接口来判断服务是否健康。 如果响应的状态是 "SERVING",则认为诊断成功。
探测结果为以下三种之一:
成功:容器通过了诊断。失败:容器未通过诊断。未知:诊断失败,因此不会采取任何行动。
3. 探针的配置
探针配置在pod的container结构体下,livenessProbe和readinessProbe参数基本一致。
3.1. 探针通用参数
常用的参数为timeoutSeconds、periodSeconds、periodSeconds,即接口超时时间,重试频率,重试次数三个值。
initialDelaySeconds:启动容器后首次进行健康检查的等待时间,单位为秒,默认值为0。timeoutSeconds:健康检查接口超时响应的时间,默认为1秒,最小值为1秒。periodSeconds:重试的频率,默认值为10秒,即10秒重试一次,最小值是1秒,建议可以设置为3-5秒。failureThreshold:失败重试的次数,默认值为3,最小值为1。successThreshold:最小探测成功次数,默认值为1,一般不设置。
除了以上的通用参数外,livenessProbe和readinessProbe参数基本一致。以下以readinessProbe为例说明探针的使用方式。
3.2. ReadinessProbe三种实现方式
3.2.1. HTTPGetAction
通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于等于400,则认为容器健康。
apiVersion: v1
kind: Pod
metadata:
name: pod-with-healthcheck
spec:
containers:
- name: nginx
image: nginx
ports:
- containnerPort: 80
readinessProbe:
httpGet:
path: /_status/healthz
port: 80
scheme: HTTP
initialDelaySeconds: 1
periodSeconds: 5
timeoutSeconds: 5
3.2.2. TCPSocketAction
通过容器IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康。
apiVersion: v1
kind: Pod
metadata:
name: pod-with-healthcheck
spec:
containers:
- name: nginx
image: nginx
ports:
- containnerPort: 80
readinessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 1
timeoutSeconds: 5
3.2.3. ExecAction
在一个容器内部执行一个命令,如果该命令状态返回值为0,则表明容器健康。
apiVersion: v1
kind: Pod
metadata:
name: readiness-exec
spec:
containers:
- name: readiness
image: tomcagcr.io/google_containers/busybox
args:
- /bin/sh
- -c
- echo ok > /tmp/health;sleep 10;rm -fr /tmp/health;sleep 600
readinessreadinessProbe:
exec:
command:
- cat
- /tmp/health
initialDelaySeconds: 1
timeoutSeconds: 5
4. 探针相关源码
探针配置在pod的container结构体下:
// 存活探针
LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,10,opt,name=livenessProbe"`
// 就绪探针
ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"`
4.1. Probe源码
type Probe struct {
// The action taken to determine the health of a container
ProbeHandler `json:",inline" protobuf:"bytes,1,opt,name=handler"`
// Number of seconds after the container has started before liveness probes are initiated.
// More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
// +optional
InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty" protobuf:"varint,2,opt,name=initialDelaySeconds"`
// Number of seconds after which the probe times out.
// Defaults to 1 second. Minimum value is 1.
// More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
// +optional
TimeoutSeconds int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,3,opt,name=timeoutSeconds"`
// How often (in seconds) to perform the probe.
// Default to 10 seconds. Minimum value is 1.
// +optional
PeriodSeconds int32 `json:"periodSeconds,omitempty" protobuf:"varint,4,opt,name=periodSeconds"`
// Minimum consecutive successes for the probe to be considered successful after having failed.
// Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1.
// +optional
SuccessThreshold int32 `json:"successThreshold,omitempty" protobuf:"varint,5,opt,name=successThreshold"`
// Minimum consecutive failures for the probe to be considered failed after having succeeded.
// Defaults to 3. Minimum value is 1.
// +optional
FailureThreshold int32 `json:"failureThreshold,omitempty" protobuf:"varint,6,opt,name=failureThreshold"`
// Optional duration in seconds the pod needs to terminate gracefully upon probe failure.
// The grace period is the duration in seconds after the processes running in the pod are sent
// a termination signal and the time when the processes are forcibly halted with a kill signal.
// Set this value longer than the expected cleanup time for your process.
// If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this
// value overrides the value provided by the pod spec.
// Value must be non-negative integer. The value zero indicates stop immediately via
// the kill signal (no opportunity to shut down).
// This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.
// Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset.
// +optional
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty" protobuf:"varint,7,opt,name=terminationGracePeriodSeconds"`
}
4.2. ProbeHandler源码
// ProbeHandler defines a specific action that should be taken in a probe.
// One and only one of the fields must be specified.
type ProbeHandler struct {
// Exec specifies the action to take.
// +optional
Exec *ExecAction `json:"exec,omitempty" protobuf:"bytes,1,opt,name=exec"`
// HTTPGet specifies the http request to perform.
// +optional
HTTPGet *HTTPGetAction `json:"httpGet,omitempty" protobuf:"bytes,2,opt,name=httpGet"`
// TCPSocket specifies an action involving a TCP port.
// +optional
TCPSocket *TCPSocketAction `json:"tcpSocket,omitempty" protobuf:"bytes,3,opt,name=tcpSocket"`
// GRPC specifies an action involving a GRPC port.
// This is a beta field and requires enabling GRPCContainerProbe feature gate.
// +featureGate=GRPCContainerProbe
// +optional
GRPC *GRPCAction `json:"grpc,omitempty" protobuf:"bytes,4,opt,name=grpc"`
}
4.3. ProbeAction
4.3.1. HTTPGetAction
// HTTPHeader describes a custom header to be used in HTTP probes
type HTTPHeader struct {
// The header field name
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// The header field value
Value string `json:"value" protobuf:"bytes,2,opt,name=value"`
}
// HTTPGetAction describes an action based on HTTP Get requests.
type HTTPGetAction struct {
// Path to access on the HTTP server.
// +optional
Path string `json:"path,omitempty" protobuf:"bytes,1,opt,name=path"`
// Name or number of the port to access on the container.
// Number must be in the range 1 to 65534.
// Name must be an IANA_SVC_NAME.
Port intstr.IntOrString `json:"port" protobuf:"bytes,2,opt,name=port"`
// Host name to connect to, defaults to the pod IP. You probably want to set
// "Host" in httpHeaders instead.
// +optional
Host string `json:"host,omitempty" protobuf:"bytes,3,opt,name=host"`
// Scheme to use for connecting to the host.
// Defaults to HTTP.
// +optional
Scheme URIScheme `json:"scheme,omitempty" protobuf:"bytes,4,opt,name=scheme,casttype=URIScheme"`
// Custom headers to set in the request. HTTP allows repeated headers.
// +optional
HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty" protobuf:"bytes,5,rep,name=httpHeaders"`
}
// URIScheme identifies the scheme used for connection to a host for Get actions
// +enum
type URIScheme string
const (
// URISchemeHTTP means that the scheme used will be http://
URISchemeHTTP URIScheme = "HTTP"
// URISchemeHTTPS means that the scheme used will be https://
URISchemeHTTPS URIScheme = "HTTPS"
)
4.3.2. TCPSocketAction
// TCPSocketAction describes an action based on opening a socket
type TCPSocketAction struct {
// Number or name of the port to access on the container.
// Number must be in the range 1 to 65534.
// Name must be an IANA_SVC_NAME.
Port intstr.IntOrString `json:"port" protobuf:"bytes,1,opt,name=port"`
// Optional: Host name to connect to, defaults to the pod IP.
// +optional
Host string `json:"host,omitempty" protobuf:"bytes,2,opt,name=host"`
}
4.3.3. ExecAction
// ExecAction describes a "run in container" action.
type ExecAction struct {
// Command is the command line to execute inside the container, the working directory for the
// command is root ('/') in the container's filesystem. The command is simply exec'd, it is
// not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use
// a shell, you need to explicitly call out to that shell.
// Exit status of 0 is treated as live/healthy and non-zero is unhealthy.
// +optional
Command []string `json:"command,omitempty" protobuf:"bytes,1,rep,name=command"`
}
4.3.4. GRPCAction
type GRPCAction struct {
// Port number of the gRPC service. Number must be in the range 1 to 65534.
Port int32 `json:"port" protobuf:"bytes,1,opt,name=port"`
// Service is the name of the service to place in the gRPC HealthCheckRequest
// (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).
//
// If this is not specified, the default behavior is defined by gRPC.
// +optional
// +default=""
Service *string `json:"service" protobuf:"bytes,2,opt,name=service"`
}
参考文章:
3.3.5 - Pod存储卷
Pod Volume
同一个Pod中的多个容器可以共享Pod级别的存储卷Volume,Volume可以定义为各种类型,多个容器各自进行挂载,将Pod的Volume挂载为容器内部需要的目录。
例如:Pod级别的Volume:"app-logs",用于tomcat向其中写日志文件,busybox读日志文件。

pod-volumes-applogs.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: tomcat
image: tomcat
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs
mountPath: /usr/local/tomcat/logs
- name: busybox
image: busybox
command: ["sh","-c","tailf /logs/catalina*.log"]
volumeMounts:
- name: app-logs
mountPath: /logs
volumes:
- name: app-logs
emptuDir: {}
查看日志
- kubectl logs
<pod_name>-c<container_name> - kubectl exec -it
<pod_name>-c<container_name>– tail /usr/local/tomcat/logs/catalina.xx.log
参考文章
- 《Kubernetes权威指南》
3.3.6 - Pod调度
Pod调度
在kubernetes集群中,Pod(container)是应用的载体,一般通过RC、Deployment、DaemonSet、Job等对象来完成Pod的调度与自愈功能。
1. RC、Deployment:全自动调度
RC的功能即保持集群中始终运行着指定个数的Pod。
在调度策略上主要有:
- 系统内置调度算法[最优Node]
- NodeSelector[定向调度]
- NodeAffinity[亲和性调度]
2. NodeSelector[定向调度]
k8s中kube-scheduler负责实现Pod的调度,内部系统通过一系列算法最终计算出最佳的目标节点。如果需要将Pod调度到指定Node上,则可以通过Node的标签(Label)和Pod的nodeSelector属性相匹配来达到目的。
1、kubectl label nodes {node-name} {label-key}={label-value}
2、nodeSelector: {label-key}:{label-value}
如果给多个Node打了相同的标签,则scheduler会根据调度算法从这组Node中选择一个可用的Node来调度。
如果Pod的nodeSelector的标签在Node中没有对应的标签,则该Pod无法被调度成功。
Node标签的使用场景:
对集群中不同类型的Node打上不同的标签,可控制应用运行Node的范围。例如role=frontend;role=backend;role=database。
3. NodeAffinity[亲和性调度]
NodeAffinity意为Node亲和性调度策略,NodeSelector为精确匹配,NodeAffinity为条件范围匹配,通过In(属于)、NotIn(不属于)、Exists(存在一个条件)、DoesNotExist(不存在)、Gt(大于)、Lt(小于)等操作符来选择Node,使调度更加灵活。
- RequiredDuringSchedulingRequiredDuringExecution:类似于NodeSelector,但在Node不满足条件时,系统将从该Node上移除之前调度上的Pod。
- RequiredDuringSchedulingIgnoredDuringExecution:与上一个类似,区别是在Node不满足条件时,系统不一定从该Node上移除之前调度上的Pod。
- PreferredDuringSchedulingIgnoredDuringExecution:指定在满足调度条件的Node中,哪些Node应更优先地进行调度。同时在Node不满足条件时,系统不一定从该Node上移除之前调度上的Pod。
如果同时设置了NodeSelector和NodeAffinity,则系统将需要同时满足两者的设置才能进行调度。
4. DaemonSet:特定场景调度
DaemonSet是kubernetes1.2版本新增的一种资源对象,用于管理在集群中每个Node上仅运行一份Pod的副本实例。

该用法适用的应用场景:
- 在每个Node上运行一个GlusterFS存储或者Ceph存储的daemon进程。
- 在每个Node上运行一个日志采集程序:fluentd或logstach。
- 在每个Node上运行一个健康程序,采集该Node的运行性能数据,例如:Prometheus Node Exportor、collectd、New Relic agent或Ganglia gmond等。
DaemonSet的Pod调度策略与RC类似,除了使用系统内置算法在每台Node上进行调度,也可以通过NodeSelector或NodeAffinity来指定满足条件的Node范围进行调度。
5. Job:批处理调度
kubernetes从1.2版本开始支持批处理类型的应用,可以通过kubernetes Job资源对象来定义并启动一个批处理任务。批处理任务通常并行(或串行)启动多个计算进程去处理一批工作项(work item),处理完后,整个批处理任务结束。
5.1. 批处理的三种模式

批处理按任务实现方式不同分为以下几种模式:
-
Job Template Expansion模式 一个Job对象对应一个待处理的Work item,有几个Work item就产生几个独立的Job,通过适用于Work item数量少,每个Work item要处理的数据量比较大的场景。例如有10个文件(Work item),每个文件(Work item)为100G。
-
Queue with Pod Per Work Item 采用一个任务队列存放Work item,一个Job对象作为消费者去完成这些Work item,其中Job会启动N个Pod,每个Pod对应一个Work item。
-
Queue with Variable Pod Count 采用一个任务队列存放Work item,一个Job对象作为消费者去完成这些Work item,其中Job会启动N个Pod,每个Pod对应一个Work item。但Pod的数量是可变的。
5.2. Job的三种类型
1)Non-parallel Jobs
通常一个Job只启动一个Pod,除非Pod异常才会重启该Pod,一旦此Pod正常结束,Job将结束。
2)Parallel Jobs with a fixed completion count
并行Job会启动多个Pod,此时需要设定Job的.spec.completions参数为一个正数,当正常结束的Pod数量达到该值则Job结束。
3)Parallel Jobs with a work queue
任务队列方式的并行Job需要一个独立的Queue,Work item都在一个Queue中存放,不能设置Job的.spec.completions参数。
此时Job的特性:
- 每个Pod能独立判断和决定是否还有任务项需要处理
- 如果某个Pod正常结束,则Job不会再启动新的Pod
- 如果一个Pod成功结束,则此时应该不存在其他Pod还在干活的情况,它们应该都处于即将结束、退出的状态
- 如果所有的Pod都结束了,且至少一个Pod成功结束,则整个Job算是成功结束
参考文章
- 《Kubernetes权威指南》
3.3.7 - Pod伸缩与升级
1. Pod伸缩
k8s中RC的用来保持集群中始终运行指定数目的实例,通过RC的scale机制可以完成Pod的扩容和缩容(伸缩)。
1.1. 手动伸缩(scale)
kubectl scale rc redis-slave --replicas=3
1.2. 自动伸缩(HPA)
Horizontal Pod Autoscaler(HPA)控制器用于实现基于CPU使用率进行自动Pod伸缩的功能。HPA控制器基于Master的kube-controller-manager服务启动参数--horizontal-pod-autoscaler-sync-period定义是时长(默认30秒),周期性监控目标Pod的CPU使用率,并在满足条件时对ReplicationController或Deployment中的Pod副本数进行调整,以符合用户定义的平均Pod CPU使用率。Pod CPU使用率来源于heapster组件,因此需安装该组件。
可以通过kubectl autoscale命令进行快速创建或者使用yaml配置文件进行创建。创建之前需已存在一个RC或Deployment对象,并且该RC或Deployment中的Pod必须定义resources.requests.cpu的资源请求值,以便heapster采集到该Pod的CPU。
1.2.1. 通过kubectl autoscale创建
例如:
php-apache-rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
name: php-apache
spec:
replicas: 1
template:
metadata:
name: php-apache
labels:
app: php-apache
spec:
containers:
- name: php-apache
image: gcr.io/google_containers/hpa-example
resources:
requests:
cpu: 200m
ports:
- containerPort: 80
创建php-apache的RC
kubectl create -f php-apache-rc.yaml
php-apache-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: php-apache
spec:
ports:
- port: 80
selector:
app: php-apache
创建php-apache的Service
kubectl create -f php-apache-svc.yaml
创建HPA控制器
kubectl autoscale rc php-apache --min=1 --max=10 --cpu-percent=50
1.2.2. 通过yaml配置文件创建
hpa-php-apache.yaml
apiVersion: v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: v1
kind: ReplicationController
name: php-apache
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 50
创建hpa
kubectl create -f hpa-php-apache.yaml
查看hpa
kubectl get hpa
2. Pod滚动升级
k8s中的滚动升级通过执行kubectl rolling-update命令完成,该命令创建一个新的RC(与旧的RC在同一个命名空间中),然后自动控制旧的RC中的Pod副本数逐渐减少为0,同时新的RC中的Pod副本数从0逐渐增加到附加值,但滚动升级中Pod副本数(包括新Pod和旧Pod)保持原预期值。
2.1. 通过配置文件实现
redis-master-controller-v2.yaml
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-master-v2
labels:
name: redis-master
version: v2
spec:
replicas: 1
selector:
name: redis-master
version: v2
template:
metadata:
labels:
name: redis-master
version: v2
spec:
containers:
- name: master
image: kubeguide/redis-master:2.0
ports:
- containerPort: 6371
注意事项:
- RC的名字(name)不能与旧RC的名字相同
- 在selector中应至少有一个Label与旧的RC的Label不同,以标识其为新的RC。例如本例中新增了version的Label。
运行kubectl rolling-update
kubectl rolling-update redis-master -f redis-master-controller-v2.yaml
2.2. 通过kubectl rolling-update命令实现
kubectl rolling-update redis-master --image=redis-master:2.0
与使用配置文件实现不同在于,该执行结果旧的RC被删除,新的RC仍使用旧的RC的名字。
2.3. 升级回滚
kubectl rolling-update加参数--rollback实现回滚操作
kubectl rolling-update redis-master --image=kubeguide/redis-master:2.0 --rollback
参考文章
- 《Kubernetes权威指南》
3.4 - 配置
3.4.1 - ConfigMap
Pod的配置管理
Kubernetes v1.2的版本提供统一的集群配置管理方案–ConfigMap。
1. ConfigMap:容器应用的配置管理
使用场景:
- 生成为容器内的环境变量。
- 设置容器启动命令的启动参数(需设置为环境变量)。
- 以Volume的形式挂载为容器内部的文件或目录。
ConfigMap以一个或多个key:value的形式保存在kubernetes系统中供应用使用,既可以表示一个变量的值(例如:apploglevel=info),也可以表示完整配置文件的内容(例如:server.xml=<?xml...>...)。
可以通过yaml配置文件或者使用kubectl create configmap命令的方式创建ConfigMap。
2. 创建ConfigMap
2.1. 通过yaml文件方式
cm-appvars.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir: /var/data
常用命令
kubectl create -f cm-appvars.yaml
kubectl get configmap
kubectl describe configmap cm-appvars
kubectl get configmap cm-appvars -o yaml
2.2. 通过kubectl命令行方式
通过kubectl create configmap创建,使用参数--from-file或--from-literal指定内容,可以在一行中指定多个参数。
1)通过--from-file参数从文件中进行创建,可以指定key的名称,也可以在一个命令行中创建包含多个key的ConfigMap。
kubectl create configmap NAME --from-file=[key=]source --from-file=[key=]source
2)通过--from-file参数从目录中进行创建,该目录下的每个配置文件名被设置为key,文件内容被设置为value。
kubectl create configmap NAME --from-file=config-files-dir
3)通过--from-literal从文本中进行创建,直接将指定的key=value创建为ConfigMap的内容。
kubectl create configmap NAME --from-literal=key1=value1 --from-literal=key2=value2
容器应用对ConfigMap的使用有两种方法:
- 通过环境变量获取ConfigMap中的内容。
- 通过Volume挂载的方式将ConfigMap中的内容挂载为容器内部的文件或目录。
2.3. 通过环境变量的方式
ConfigMap的yaml文件:cm-appvars.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir: /var/data
Pod的yaml文件:cm-test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod
spec:
containers:
- name: cm-test
image: busybox
command: ["/bin/sh","-c","env|grep APP"]
env:
- name: APPLOGLEVEL
valueFrom:
configMapKeyRef:
name: cm-appvars
key: apploglevel
- name: APPDATADIR
valueFrom:
configMapKeyRef:
name: cm-appvars
key: appdatadir
创建命令:
kubectl create -f cm-test-pod.yaml
kubectl get pods --show-all
kubectl logs cm-test-pod
3. 使用ConfigMap的限制条件
- ConfigMap必须在Pod之前创建
- ConfigMap也可以定义为属于某个Namespace。只有处于相同Namespace中的Pod可以引用它。
- kubelet只支持可以被API Server管理的Pod使用ConfigMap。静态Pod无法引用。
- 在Pod对ConfigMap进行挂载操作时,容器内只能挂载为“目录”,无法挂载为文件。
参考文章
- 《Kubernetes权威指南》
4 - 核心原理
4.1 - 核心组件
4.1.1 - Kubernetes核心原理(一)之API Server
1. API Server简介
k8s API Server提供了k8s各类资源对象(pod,RC,Service等)的增删改查及watch等HTTP Rest接口,是整个系统的数据总线和数据中心。
kubernetes API Server的功能:
- 提供了集群管理的REST API接口(包括认证授权、数据校验以及集群状态变更);
- 提供其他模块之间的数据交互和通信的枢纽(其他模块通过API Server查询或修改数据,只有API Server才直接操作etcd);
- 是资源配额控制的入口;
- 拥有完备的集群安全机制.
kube-apiserver工作原理图

2. 如何访问kubernetes API
k8s通过kube-apiserver这个进程提供服务,该进程运行在单个k8s-master节点上。默认有两个端口。
2.1. 本地端口
- 该端口用于接收HTTP请求;
- 该端口默认值为8080,可以通过API Server的启动参数“--insecure-port”的值来修改默认值;
- 默认的IP地址为“localhost”,可以通过启动参数“--insecure-bind-address”的值来修改该IP地址;
- 非认证或授权的HTTP请求通过该端口访问API Server。
2.2. 安全端口
- 该端口默认值为6443,可通过启动参数“--secure-port”的值来修改默认值;
- 默认IP地址为非本地(Non-Localhost)网络端口,通过启动参数“--bind-address”设置该值;
- 该端口用于接收HTTPS请求;
- 用于基于Tocken文件或客户端证书及HTTP Base的认证;
- 用于基于策略的授权;
- 默认不启动HTTPS安全访问控制。
2.3. 访问方式
Kubernetes REST API可参考https://kubernetes.io/docs/api-reference/v1.6/
2.3.1. curl
curl localhost:8080/api
curl localhost:8080/api/v1/pods
curl localhost:8080/api/v1/services
curl localhost:8080/api/v1/replicationcontrollers
2.3.2. Kubectl Proxy
Kubectl Proxy代理程序既能作为API Server的反向代理,也能作为普通客户端访问API Server的代理。通过master节点的8080端口来启动该代理程序。
kubectl proxy --port=8080 &
具体见kubectl proxy --help
[root@node5 ~]# kubectl proxy --help
To proxy all of the kubernetes api and nothing else, use:
kubectl proxy --api-prefix=/
To proxy only part of the kubernetes api and also some static files:
kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/
The above lets you 'curl localhost:8001/api/v1/pods'.
To proxy the entire kubernetes api at a different root, use:
kubectl proxy --api-prefix=/custom/
The above lets you 'curl localhost:8001/custom/api/v1/pods'
Usage:
kubectl proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix] [flags]
Examples:
# Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/
$ kubectl proxy --port=8011 --www=./local/www/
# Run a proxy to kubernetes apiserver on an arbitrary local port.
# The chosen port for the server will be output to stdout.
$ kubectl proxy --port=0
# Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api
# This makes e.g. the pods api available at localhost:8011/k8s-api/v1/pods/
$ kubectl proxy --api-prefix=/k8s-api
Flags:
--accept-hosts="^localhost$,^127//.0//.0//.1$,^//[::1//]$": Regular expression for hosts that the proxy should accept.
--accept-paths="^/.*": Regular expression for paths that the proxy should accept.
--api-prefix="/": Prefix to serve the proxied API under.
--disable-filter[=false]: If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks, when used with an accessible port.
-p, --port=8001: The port on which to run the proxy. Set to 0 to pick a random port.
--reject-methods="POST,PUT,PATCH": Regular expression for HTTP methods that the proxy should reject.
--reject-paths="^/api/.*/exec,^/api/.*/run": Regular expression for paths that the proxy should reject.
-u, --unix-socket="": Unix socket on which to run the proxy.
-w, --www="": Also serve static files from the given directory under the specified prefix.
-P, --www-prefix="/static/": Prefix to serve static files under, if static file directory is specified.
Global Flags:
--alsologtostderr[=false]: log to standard error as well as files
--api-version="": The API version to use when talking to the server
--certificate-authority="": Path to a cert. file for the certificate authority.
--client-certificate="": Path to a client key file for TLS.
--client-key="": Path to a client key file for TLS.
--cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context to use
--insecure-skip-tls-verify[=false]: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.
--kubeconfig="": Path to the kubeconfig file to use for CLI requests.
--log-backtrace-at=:0: when logging hits line file:N, emit a stack trace
--log-dir="": If non-empty, write log files in this directory
--log-flush-frequency=5s: Maximum number of seconds between log flushes
--logtostderr[=true]: log to standard error instead of files
--match-server-version[=false]: Require server version to match client version
--namespace="": If present, the namespace scope for this CLI request.
--password="": Password for basic authentication to the API server.
-s, --server="": The address and port of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
--user="": The name of the kubeconfig user to use
--username="": Username for basic authentication to the API server.
--v=0: log level for V logs
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
2.3.3. kubectl客户端
命令行工具kubectl客户端,通过命令行参数转换为对API Server的REST API调用,并将调用结果输出。
命令格式:kubectl [command] [options]
具体可参考k8s常用命令
2.3.4. 编程方式调用
使用场景:
1、运行在Pod里的用户进程调用kubernetes API,通常用来实现分布式集群搭建的目标。
2、开发基于kubernetes的管理平台,比如调用kubernetes API来完成Pod、Service、RC等资源对象的图形化创建和管理界面。可以使用kubernetes提供的Client Library。
具体可参考https://github.com/kubernetes/client-go。
3. 通过API Server访问Node、Pod和Service
k8s API Server最主要的REST接口是资源对象的增删改查,另外还有一类特殊的REST接口—k8s Proxy API接口,这类接口的作用是代理REST请求,即kubernetes API Server把收到的REST请求转发到某个Node上的kubelet守护进程的REST端口上,由该kubelet进程负责响应。
3.1. Node相关接口
关于Node相关的接口的REST路径为:/api/v1/proxy/nodes/{name},其中{name}为节点的名称或IP地址。
/api/v1/proxy/nodes/{name}/pods/ #列出指定节点内所有Pod的信息
/api/v1/proxy/nodes/{name}/stats/ #列出指定节点内物理资源的统计信息
/api/v1/prxoy/nodes/{name}/spec/ #列出指定节点的概要信息
这里获取的Pod信息来自Node而非etcd数据库,两者时间点可能存在偏差。如果在kubelet进程启动时加--enable-debugging-handles=true参数,那么kubernetes Proxy API还会增加以下接口:
/api/v1/proxy/nodes/{name}/run #在节点上运行某个容器
/api/v1/proxy/nodes/{name}/exec #在节点上的某个容器中运行某条命令
/api/v1/proxy/nodes/{name}/attach #在节点上attach某个容器
/api/v1/proxy/nodes/{name}/portForward #实现节点上的Pod端口转发
/api/v1/proxy/nodes/{name}/logs #列出节点的各类日志信息
/api/v1/proxy/nodes/{name}/metrics #列出和该节点相关的Metrics信息
/api/v1/proxy/nodes/{name}/runningpods #列出节点内运行中的Pod信息
/api/v1/proxy/nodes/{name}/debug/pprof #列出节点内当前web服务的状态,包括CPU和内存的使用情况
3.2. Pod相关接口
/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*} #访问pod的某个服务接口
/api/v1/proxy/namespaces/{namespace}/pods/{name} #访问Pod
#以下写法不同,功能一样
/api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*} #访问pod的某个服务接口
/api/v1/namespaces/{namespace}/pods/{name}/proxy #访问Pod
3.3. Service相关接口
/api/v1/proxy/namespaces/{namespace}/services/{name}
Pod的proxy接口的作用:在kubernetes集群之外访问某个pod容器的服务(HTTP服务),可以用Proxy API实现,这种场景多用于管理目的,比如逐一排查Service的Pod副本,检查哪些Pod的服务存在异常问题。
4. 集群功能模块之间的通信
kubernetes API Server作为集群的核心,负责集群各功能模块之间的通信,集群内各个功能模块通过API Server将信息存入etcd,当需要获取和操作这些数据时,通过API Server提供的REST接口(GET/LIST/WATCH方法)来实现,从而实现各模块之间的信息交互。
4.1. kubelet与API Server交互
每个Node节点上的kubelet定期就会调用API Server的REST接口报告自身状态,API Server接收这些信息后,将节点状态信息更新到etcd中。kubelet也通过API Server的Watch接口监听Pod信息,从而对Node机器上的POD进行管理。
| 监听信息 | kubelet动作 |
|---|---|
| 新的POD副本被调度绑定到本节点 | 执行POD对应的容器的创建和启动逻辑 |
| POD对象被删除 | 删除本节点上相应的POD容器 |
| 修改POD信息 | 修改本节点的POD容器 |
4.2. kube-controller-manager与API Server交互
kube-controller-manager中的Node Controller模块通过API Server提供的Watch接口,实时监控Node的信息,并做相应处理。
4.3. kube-scheduler与API Server交互
Scheduler通过API Server的Watch接口监听到新建Pod副本的信息后,它会检索所有符合该Pod要求的Node列表,开始执行Pod调度逻辑。调度成功后将Pod绑定到目标节点上。
4.4. 特别说明
为了缓解各模块对API Server的访问压力,各功能模块都采用缓存机制来缓存数据,各功能模块定时从API Server获取指定的资源对象信息(LIST/WATCH方法),然后将信息保存到本地缓存,功能模块在某些情况下不直接访问API Server,而是通过访问缓存数据来间接访问API Server。
参考《kubernetes权威指南》
4.1.2 - Kubernetes核心原理(二)之Controller Manager
1. Controller Manager简介
Controller Manager作为集群内部的管理控制中心,负责集群内的Node、Pod副本、服务端点(Endpoint)、命名空间(Namespace)、服务账号(ServiceAccount)、资源定额(ResourceQuota)的管理,当某个Node意外宕机时,Controller Manager会及时发现并执行自动化修复流程,确保集群始终处于预期的工作状态。

每个Controller通过API Server提供的接口实时监控整个集群的每个资源对象的当前状态,当发生各种故障导致系统状态发生变化时,会尝试将系统状态修复到“期望状态”。
2. Replication Controller
为了区分,将资源对象Replication Controller简称RC,而本文中是指Controller Manager中的Replication Controller,称为副本控制器。副本控制器的作用即保证集群中一个RC所关联的Pod副本数始终保持预设值。
- 只有当Pod的重启策略是Always的时候(RestartPolicy=Always),副本控制器才会管理该Pod的操作(创建、销毁、重启等)。
- RC中的Pod模板就像一个模具,模具制造出来的东西一旦离开模具,它们之间就再没关系了。一旦Pod被创建,无论模板如何变化,也不会影响到已经创建的Pod。
- Pod可以通过修改label来脱离RC的管控,该方法可以用于将Pod从集群中迁移,数据修复等调试。
- 删除一个RC不会影响它所创建的Pod,如果要删除Pod需要将RC的副本数属性设置为0。
- 不要越过RC创建Pod,因为RC可以实现自动化控制Pod,提高容灾能力。
2.1. Replication Controller的职责
- 确保集群中有且仅有N个Pod实例,N是RC中定义的Pod副本数量。
- 通过调整RC中的spec.replicas属性值来实现系统扩容或缩容。
- 通过改变RC中的Pod模板来实现系统的滚动升级。
2.2. Replication Controller使用场景
| 使用场景 | 说明 | 使用命令 |
|---|---|---|
| 重新调度 | 当发生节点故障或Pod被意外终止运行时,可以重新调度保证集群中仍然运行指定的副本数。 | |
| 弹性伸缩 | 通过手动或自动扩容代理修复副本控制器的spec.replicas属性,可以实现弹性伸缩。 | kubectl scale |
| 滚动更新 | 创建一个新的RC文件,通过kubectl 命令或API执行,则会新增一个新的副本同时删除旧的副本,当旧副本为0时,删除旧的RC。 | kubectl rolling-update |
滚动升级,具体可参考kubectl rolling-update --help,官方文档:https://kubernetes.io/docs/tasks/run-application/rolling-update-replication-controller/
3. Node Controller
kubelet在启动时会通过API Server注册自身的节点信息,并定时向API Server汇报状态信息,API Server接收到信息后将信息更新到etcd中。
Node Controller通过API Server实时获取Node的相关信息,实现管理和监控集群中的各个Node节点的相关控制功能。流程如下

1、Controller Manager在启动时如果设置了--cluster-cidr参数,那么为每个没有设置Spec.PodCIDR的Node节点生成一个CIDR地址,并用该CIDR地址设置节点的Spec.PodCIDR属性,防止不同的节点的CIDR地址发生冲突。
2、具体流程见以上流程图。
3、逐个读取节点信息,如果节点状态变成非“就绪”状态,则将节点加入待删除队列,否则将节点从该队列删除。
4. ResourceQuota Controller
资源配额管理确保指定的资源对象在任何时候都不会超量占用系统物理资源。
支持三个层次的资源配置管理:
1)容器级别:对CPU和Memory进行限制
2)Pod级别:对一个Pod内所有容器的可用资源进行限制
3)Namespace级别:包括
- Pod数量
- Replication Controller数量
- Service数量
- ResourceQuota数量
- Secret数量
- 可持有的PV(Persistent Volume)数量
说明:
- k8s配额管理是通过Admission Control(准入控制)来控制的;
- Admission Control提供两种配额约束方式:LimitRanger和ResourceQuota;
- LimitRanger作用于Pod和Container;
- ResourceQuota作用于Namespace上,限定一个Namespace里的各类资源的使用总额。
ResourceQuota Controller流程图:

5. Namespace Controller
用户通过API Server可以创建新的Namespace并保存在etcd中,Namespace Controller定时通过API Server读取这些Namespace信息。
如果Namespace被API标记为优雅删除(即设置删除期限,DeletionTimestamp),则将该Namespace状态设置为“Terminating”,并保存到etcd中。同时Namespace Controller删除该Namespace下的ServiceAccount、RC、Pod等资源对象。
6. Endpoint Controller
Service、Endpoint、Pod的关系:

Endpoints表示了一个Service对应的所有Pod副本的访问地址,而Endpoints Controller负责生成和维护所有Endpoints对象的控制器。它负责监听Service和对应的Pod副本的变化。
- 如果监测到Service被删除,则删除和该Service同名的Endpoints对象;
- 如果监测到新的Service被创建或修改,则根据该Service信息获得相关的Pod列表,然后创建或更新Service对应的Endpoints对象。
- 如果监测到Pod的事件,则更新它对应的Service的Endpoints对象。
kube-proxy进程获取每个Service的Endpoints,实现Service的负载均衡功能。
7. Service Controller
Service Controller是属于kubernetes集群与外部的云平台之间的一个接口控制器。Service Controller监听Service变化,如果是一个LoadBalancer类型的Service,则确保外部的云平台上对该Service对应的LoadBalancer实例被相应地创建、删除及更新路由转发表。
参考《Kubernetes权威指南》
4.1.3 - Kubernetes核心原理(三)之Scheduler
1. Scheduler简介
Scheduler负责Pod调度。在整个系统中起"承上启下"作用,承上:负责接收Controller Manager创建的新的Pod,为其选择一个合适的Node;启下:Node上的kubelet接管Pod的生命周期。
Scheduler:
1)通过调度算法为待调度Pod列表的每个Pod从Node列表中选择一个最适合的Node,并将信息写入etcd中
2)kubelet通过API Server监听到kubernetes Scheduler产生的Pod绑定信息,然后获取对应的Pod清单,下载Image,并启动容器。

2. 调度流程
1、预选调度过程,即遍历所有目标Node,筛选出符合要求的候选节点,kubernetes内置了多种预选策略(xxx Predicates)供用户选择
2、确定最优节点,在第一步的基础上采用优选策略(xxx Priority)计算出每个候选节点的积分,取最高积分。
调度流程通过插件式加载的“调度算法提供者”(AlgorithmProvider)具体实现,一个调度算法提供者就是包括一组预选策略与一组优选策略的结构体。
3. 预选策略
说明:返回true表示该节点满足该Pod的调度条件;返回false表示该节点不满足该Pod的调度条件。
3.1. NoDiskConflict
判断备选Pod的数据卷是否与该Node上已存在Pod挂载的数据卷冲突,如果是则返回false,否则返回true。
3.2. PodFitsResources
判断备选节点的资源是否满足备选Pod的需求,即节点的剩余资源满不满足该Pod的资源使用。
- 计算备选Pod和节点中已用资源(该节点所有Pod的使用资源)的总和。
- 获取备选节点的状态信息,包括节点资源信息。
- 如果(备选Pod+节点已用资源>该节点总资源)则返回false,即剩余资源不满足该Pod使用;否则返回true。
3.3. PodSelectorMatches
判断节点是否包含备选Pod的标签选择器指定的标签,即通过标签来选择Node。
- 如果Pod中没有指定spec.nodeSelector,则返回true。
- 否则获得备选节点的标签信息,判断该节点的标签信息中是否包含该Pod的spec.nodeSelector中指定的标签,如果包含返回true,否则返回false。
3.4. PodFitsHost
判断备选Pod的spec.nodeName所指定的节点名称与备选节点名称是否一致,如果一致返回true,否则返回false。
3.5. CheckNodeLabelPresence
检查备选节点中是否有Scheduler配置的标签,如果有返回true,否则返回false。
3.6. CheckServiceAffinity
判断备选节点是否包含Scheduler配置的标签,如果有返回true,否则返回false。
3.7. PodFitsPorts
判断备选Pod所用的端口列表中的端口是否在备选节点中已被占用,如果被占用返回false,否则返回true。
4. 优选策略
4.1. LeastRequestedPriority
优先从备选节点列表中选择资源消耗最小的节点(CPU+内存)。
4.2. CalculateNodeLabelPriority
优先选择含有指定Label的节点。
4.3. BalancedResourceAllocation
优先从备选节点列表中选择各项资源使用率最均衡的节点。
参考《Kubernetes权威指南》
4.1.4 - Kubernetes核心原理(四)之kubelet
1. kubelet简介
在kubernetes集群中,每个Node节点都会启动kubelet进程,用来处理Master节点下发到本节点的任务,管理Pod和其中的容器。kubelet会在API Server上注册节点信息,定期向Master汇报节点资源使用情况,并通过cAdvisor监控容器和节点资源。可以把kubelet理解成【Server-Agent】架构中的agent,是Node上的pod管家。
更多kubelet配置参数信息可参考kubelet --help
2. 节点管理
节点通过设置kubelet的启动参数“--register-node”,来决定是否向API Server注册自己,默认为true。可以通过kubelet --help或者查看kubernetes源码【cmd/kubelet/app/server.go中】来查看该参数。
kubelet的配置文件
默认配置文件在/etc/kubernetes/kubelet中,其中
- --api-servers:用来配置Master节点的IP和端口。
- --kubeconfig:用来配置kubeconfig的路径,kubeconfig文件常用来指定证书。
- --hostname-override:用来配置该节点在集群中显示的主机名。
- --node-status-update-frequency:配置kubelet向Master心跳上报的频率,默认为10s。
3. Pod管理
kubelet有几种方式获取自身Node上所需要运行的Pod清单。但本文只讨论通过API Server监听etcd目录,同步Pod列表的方式。
kubelet通过API Server Client使用WatchAndList的方式监听etcd中/registry/nodes/${当前节点名称}和/registry/pods的目录,将获取的信息同步到本地缓存中。
kubelet监听etcd,执行对Pod的操作,对容器的操作则是通过Docker Client执行,例如启动删除容器等。
kubelet创建和修改Pod流程:
- 为该Pod创建一个数据目录。
- 从API Server读取该Pod清单。
- 为该Pod挂载外部卷(External Volume)
- 下载Pod用到的Secret。
- 检查运行的Pod,执行Pod中未完成的任务。
- 先创建一个Pause容器,该容器接管Pod的网络,再创建其他容器。
- Pod中容器的处理流程: 1)比较容器hash值并做相应处理。 2)如果容器被终止了且没有指定重启策略,则不做任何处理。 3)调用Docker Client下载容器镜像,调用Docker Client运行容器。
4. 容器健康检查
Pod通过探针的方式来检查容器的健康状态,具体可参考Pod详解#Pod健康检查。
5. cAdvisor资源监控
kubelet通过cAdvisor获取本节点信息及容器的数据。cAdvisor为谷歌开源的容器资源分析工具,默认集成到kubernetes中。
cAdvisor自动采集CPU,内存,文件系统,网络使用情况,容器中运行的进程,默认端口为4194。可以通过Node IP+Port访问。
更多参考:http://github.com/google/cadvisor
参考《Kubernetes权威指南》
4.2 - 流程图
4.2.1 - Pod创建流程
Pod创建基本流程图
Pod创建完整流程图
图片来源:https://fuckcloudnative.io/posts/what-happens-when-k8s/
参考:
4.2.2 - PVC创建流程
pvc流程
流程如下:
- 用户创建了一个包含 PVC 的 Pod,该 PVC 要求使用动态存储卷;
- Scheduler 根据 Pod 配置、节点状态、PV 配置等信息,把 Pod 调度到一个合适的 Worker 节点上;
- PV 控制器 watch 到该 Pod 使用的 PVC 处于 Pending 状态,于是调用 Volume Plugin(in-tree)创建存储卷,并创建 PV 对象(out-of-tree 由 External Provisioner 来处理);
- AD 控制器发现 Pod 和 PVC 处于待挂接状态,于是调用 Volume Plugin 挂接存储设备到目标 Worker 节点上
- 在 Worker 节点上,Kubelet 中的 Volume Manager 等待存储设备挂接完成,并通过 Volume Plugin 将设备挂载到全局目录:/var/lib/kubelet/pods/[pod uid]/volumes/kubernetes.io~iscsi/[PVname](以 iscsi 为例);
- Kubelet 通过 Docker 启动 Pod 的 Containers,用 bind mount 方式将已挂载到本地全局目录的卷映射到容器中。
详细流程图
5 - 容器网络
5.1 - Docker网络
1. Docker的网络基础
1.1. Network Namespace
不同的网络命名空间中,协议栈是独立的,完全隔离,彼此之间无法通信。同一个网络命名空间有独立的路由表和独立的Iptables/Netfilter来提供包的转发、NAT、IP包过滤等功能。
1.1.1. 网络命名空间的实现
将与网络协议栈相关的全局变量变成一个Net Namespace变量的成员,然后在调用协议栈函数中加入一个Namepace参数。
1.1.2. 网络命名空间的操作
1、创建网络命名空间
ip netns add name
2、在命名空间内执行命令
ip netns exec name command
3、进入命名空间
ip netns exec name bash
2. Docker的网络实现
2.1. 容器网络
Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
Docker网桥是宿主机虚拟出来的,并不是真实存在的网络设备,外部网络是无法寻址到的,这也意味着外部网络无法通过直接Container-IP访问到容器。如果容器希望外部访问能够访问到,可以通过映射容器端口到宿主主机(端口映射),即docker run创建容器时候通过 -p 或 -P 参数来启用,访问容器的时候就通过[宿主机IP]:[容器端口]访问容器。

2.2. 4类网络模式
| Docker网络模式 | 配置 | 说明 |
|---|---|---|
| host模式 | --net=host | 容器和宿主机共享Network namespace。 |
| container模式 | --net=container:NAME_or_ID | 容器和另外一个容器共享Network namespace。 kubernetes中的pod就是多个容器共享一个Network namespace。 |
| none模式 | --net=none | 容器有独立的Network namespace,但并没有对其进行任何网络设置,如分配veth pair 和网桥连接,配置IP等。 |
| bridge模式 | --net=bridge(默认为该模式) | 桥接模式 |
3. Docker网络模式
3.1. bridge桥接模式
在bridge模式下,Docker可以使用独立的网络栈。实现方式是父进程在创建子进程的时候通过传入CLONE_NEWNET的参数创建出一个网络命名空间。
实现步骤:
- Docker Daemon首次启动时会创建一个虚拟网桥docker0,地址通常为172.x.x.x开头,在私有的网络空间中给这个网络分配一个子网。
- 由Docker创建处理的每个容器,都会创建一个虚拟以太设备对(veth pair),一端关联到网桥,另一端使用Namespace技术映射到容器内的eth0设备,然后从网桥的地址段内给eth0接口分配一个IP地址。

一般情况,宿主机IP与docker0 IP、容器IP是不同的IP段,默认情况,外部看不到docker0和容器IP,对于外部来说相当于docker0和容器的IP为内网IP。
3.1.1. 外部网络访问Docker容器
外部访问docker容器可以通过端口映射(NAT)的方式,Docker使用NAT的方式将容器内部的服务与宿主机的某个端口port_1绑定。
外部访问容器的流程如下:
- 外界网络通过宿主机的IP和映射的端口port_1访问。
- 当宿主机收到此类请求,会通过DNAT将请求的目标IP即宿主机IP和目标端口即映射端口port_1替换成容器的IP和容器的端口port_0。
- 由于宿主机上可以识别容器IP,所以宿主机将请求发给veth pair。
- veth pair将请求发送给容器内部的eth0,由容器内部的服务进行处理。
3.1.2. Docker容器访问外部网络
docker容器访问外部网络的流程:
-
docker容器向外部目标IP和目标端口port_2发起请求,请求报文中的源IP为容器IP。
-
请求通过容器内部的eth0到veth pair的另一端docker0网桥。
-
docker0网桥通过数据报转发功能将请求转发到宿主机的eth0。
-
宿主机处理请求时通过SNAT将请求中的源IP换成宿主机eth0的IP。
-
处理后的报文通过请求的目标IP发送到外部网络。
3.1.3. 缺点
使用NAT的方式可能会带来性能的问题,影响网络传输效率。
3.2. host模式
host模式并没有给容器创建一个隔离的网络环境,而是和宿主机共用一个网络命名空间,容器使用宿主机的eth0和外界进行通信,同样容器也共用宿主机的端口资源,即分配端口可能存在与宿主机已分配的端口冲突的问题。
实现的方式即父进程在创建子进程的时候不传入CLONE_NEWNET的参数,从而和宿主机共享一个网络空间。
host模式没有通过NAT的方式进行转发因此性能上相对较好,但是不存在网络隔离性,可能产生端口冲突的问题。
3.3. container模式
container模式即docker容器可以使用其他容器的网络命名空间,即和其他容器处于同一个网络命名空间。
步骤:
- 查找其他容器的网络命名空间。
- 新创建的容器的网络命名空间使用其他容器的网络命名空间。
通过和其他容器共享网络命名空间的方式,可以让不同的容器之间处于相同的网络命名空间,可以直接通过localhost的方式进行通信,简化了强关联的多个容器之间的通信问题。
k8s中的pod的概念就是通过一组容器共享一个网络命名空间来达到pod内部的不同容器可以直接通过localhost的方式进行通信。
3.4. none模式
none模式即不为容器创建任何的网络环境,用户可以根据自己的需要手动去创建不同的网络定制配置。
参考:
- 《Docker源码分析》
5.2 - K8S网络
1. kubernetes网络模型
1.1. 基础原则
- 每个Pod都拥有一个独立的IP地址,而且假定所有Pod都在一个可以直接连通的、扁平的网络空间中,不管是否运行在同一Node上都可以通过Pod的IP来访问。
- k8s中Pod的IP是最小粒度IP。同一个Pod内所有的容器共享一个网络堆栈,该模型称为IP-per-Pod模型。
- Pod由docker0实际分配的IP,Pod内部看到的IP地址和端口与外部保持一致。同一个Pod内的不同容器共享网络,可以通过localhost来访问对方的端口,类似同一个VM内的不同进程。
- IP-per-Pod模型从端口分配、域名解析、服务发现、负载均衡、应用配置等角度看,Pod可以看作是一台独立的VM或物理机。
1.2. k8s对集群的网络要求
- 所有容器都可以不用NAT的方式同别的容器通信。
- 所有节点都可以在不同NAT的方式下同所有容器通信,反之亦然。
- 容器的地址和别人看到的地址是同一个地址。
以上的集群网络要求可以通过第三方开源方案实现,例如flannel。
1.3. 网络架构图

1.4. k8s集群IP概念汇总
由集群外部到集群内部:
| IP类型 | 说明 |
|---|---|
| Proxy-IP | 代理层公网地址IP,外部访问应用的网关服务器。[实际需要关注的IP] |
| Service-IP | Service的固定虚拟IP,Service-IP是内部,外部无法寻址到。 |
| Node-IP | 容器宿主机的主机IP。 |
| Container-Bridge-IP | 容器网桥(docker0)IP,容器的网络都需要通过容器网桥转发。 |
| Pod-IP | Pod的IP,等效于Pod中网络容器的Container-IP。 |
| Container-IP | 容器的IP,容器的网络是个隔离的网络空间。 |
2. kubernetes的网络实现
k8s网络场景
- 容器与容器之间的直接通信。
- Pod与Pod之间的通信。
- Pod到Service之间的通信。
- 集群外部与内部组件之间的通信。
2.1. Pod网络
Pod作为kubernetes的最小调度单元,Pod是容器的集合,是一个逻辑概念,Pod包含的容器都运行在同一个宿主机上,这些容器将拥有同样的网络空间,容器之间能够互相通信,它们能够在本地访问其它容器的端口。 实际上Pod都包含一个网络容器,它不做任何事情,只是用来接管Pod的网络,业务容器通过加入网络容器的网络从而实现网络共享。Pod网络本质上还是容器网络,所以Pod-IP就是网络容器的Container-IP。
一般将容器云平台的网络模型打造成一个扁平化网络平面,在这个网络平面内,Pod作为一个网络单元同Kubernetes Node的网络处于同一层级。
2.2. Pod内部容器之间的通信
同一个Pod之间的不同容器因为共享同一个网络命名空间,所以可以直接通过localhost直接通信。
2.3. Pod之间的通信
2.3.1. 同Node的Pod之间的通信
同一个Node内,不同的Pod都有一个全局IP,可以直接通过Pod的IP进行通信。Pod地址和docker0在同一个网段。
在pause容器启动之前,会创建一个虚拟以太网接口对(veth pair),该接口对一端连着容器内部的eth0 ,一端连着容器外部的vethxxx,vethxxx会绑定到容器运行时配置使用的网桥bridge0上,从该网络的IP段中分配IP给容器的eth0。
当同节点上的Pod-A发包给Pod-B时,包传送路线如下:
pod-a的eth0—>pod-a的vethxxx—>bridge0—>pod-b的vethxxx—>pod-b的eth0
因为相同节点的bridge0是相通的,因此可以通过bridge0来完成不同pod直接的通信,但是不同节点的bridge0是不通的,因此不同节点的pod之间的通信需要将不同节点的bridge0给连接起来。
2.3.2. 不同Node的Pod之间的通信
不同的Node之间,Node的IP相当于外网IP,可以直接访问,而Node内的docker0和Pod的IP则是内网IP,无法直接跨Node访问。需要通过Node的网卡进行转发。
所以不同Node之间的通信需要达到两个条件:
- 对整个集群中的Pod-IP分配进行规划,不能有冲突(可以通过第三方开源工具来管理,例如flannel)。
- 将Node-IP与该Node上的Pod-IP关联起来,通过Node-IP再转发到Pod-IP。
不同节点的Pod之间的通信需要将不同节点的bridge0给连接起来。连接不同节点的bridge0的方式有好几种,主要有overlay和underlay,或常规的三层路由。
不同节点的bridge0需要不同的IP段,保证Pod IP分配不会冲突,节点的物理网卡eth0也要和该节点的网桥bridge0连接。因此,节点a上的pod-a发包给节点b上的pod-b,路线如下:
节点a上的pod-a的eth0—>pod-a的vethxxx—>节点a的bridge0—>节点a的eth0—>
节点b的eth0—>节点b的bridge0—>pod-b的vethxxx—>pod-b的eth0

1. Pod间实现通信
例如:Pod1和Pod2(同主机),Pod1和Pod3(跨主机)能够通信
实现:因为Pod的Pod-IP是Docker网桥分配的,Pod-IP是同Node下全局唯一的。所以将不同Kubernetes Node的 Docker网桥配置成不同的IP网段即可。
2. Node与Pod间实现通信
例如:Node1和Pod1/ Pod2(同主机),Pod3(跨主机)能够通信
实现:在容器集群中创建一个覆盖网络(Overlay Network),联通各个节点,目前可以通过第三方网络插件来创建覆盖网络,比如Flannel和Open vSwitch等。
不同节点间的Pod访问也可以通过calico形成的Pod IP的路由表来解决。
2.4. Service网络
Service的就是在Pod之间起到服务代理的作用,对外表现为一个单一访问接口,将请求转发给Pod,Service的网络转发是Kubernetes实现服务编排的关键一环。Service都会生成一个虚拟IP,称为Service-IP, Kuberenetes Porxy组件负责实现Service-IP路由和转发,在容器覆盖网络之上又实现了虚拟转发网络。
Kubernetes Porxy实现了以下功能:
- 转发访问Service的Service-IP的请求到Endpoints(即Pod-IP)。
- 监控Service和Endpoints的变化,实时刷新转发规则。
- 负载均衡能力。
3. 开源的网络组件
CNI插件选型:CNI插件选型
参考
- 《Kubernetes权威指南》
5.3 - Pod的DNS策略
1. Pod的DNS策略
可以在pod中定义dnsPolicy字段来设置dns的策略。
-
"
Default": Pod 从运行所在的节点继承名称解析配置。就是该Pod的DNS配置会跟宿主机完全一致。 -
"
ClusterFirst": 如果没有配置,即为默认的DNS策略。预先把kube-dns(或CoreDNS)的信息当作预设参数写入到该Pod内的DNS配置。与配置的集群域后缀不匹配的任何 DNS 查询(例如 "www.kubernetes.io") 都会由 DNS 服务器转发到上游名称服务器。 -
"
ClusterFirstWithHostNet": 对于以 hostNetwork 方式运行的 Pod,应将其 DNS 策略显式设置为 "ClusterFirstWithHostNet"。否则,以 hostNetwork 方式和"ClusterFirst"策略运行的 Pod 将会做出回退至"Default"策略的行为。 -
"
None": 此设置允许 Pod 忽略 Kubernetes 环境中的 DNS 设置。Pod 会使用其dnsConfig字段所提供的 DNS 设置。
2. Pod DNS的配置
当 Pod 的 dnsPolicy 设置为 "None" 时,必须指定 dnsConfig 字段。
dnsConfig 字段中属性:
-
nameservers:将用作于 Pod 的 DNS 服务器的 IP 地址列表。 最多可以指定 3 个 IP 地址。例如 coredns的Cluster IP。 -
searches:用于在 Pod 中查找主机名的 DNS 搜索域的列表。此属性是可选的。 -
options:可选的对象列表,其中每个对象可能具有name属性(必需)和value属性(可选)。
示例:
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "None"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options:
- name: ndots
value: "2"
- name: edns0
通过以上配置,容器内的/etc/resolv.conf文件内容为:
kubectl exec -it dns-example -- cat /etc/resolv.conf
nameserver 1.2.3.4
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0
3. 自定义DNS服务
默认一般使用coredns来作为k8s的dns服务器。默认使用deployment的方式来运行coredns,会创建一个名为kube-dns的service,并用ClusterIP(默认为10.96.0.10)来作为集群内的pod的nameserver。
kubelet 使用 --cluster-dns=<DNS 服务 IP> 标志将 DNS 解析器的信息传递给每个容器。使用 --cluster-domain=<默认本地域名> 标志配置本地域名。
可查看默认配置:
# cat /var/lib/kubelet/config.yaml
...
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
总结:
当没有给pod设置任何dns策略时,则默认使用ClusterFirst的策略,即nameserver的IP为coredns的ClusterIP。通过coredns来解析服务。
3.1. 配置继承节点的DNS解析
-
如果 Pod 的
dnsPolicy设置为default,则它将从 Pod 运行所在节点继承名称解析配置。 -
使用 kubelet 的
--resolv-conf标志设置为宿主机的/etc/resolv.conf文件。
3.2. 配置CoreDNS
配置
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefi: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
配置说明:
Corefile 配置包括以下 CoreDNS 插件:
-
errors:错误记录到标准输出。
-
health:在
http://localhost:8080/health处提供 CoreDNS 的健康报告。 在这个扩展语法中,lameduck会使此进程不健康,等待 5 秒后进程被关闭。 -
ready:在端口 8181 上提供的一个 HTTP 端点, 当所有能够表达自身就绪的插件都已就绪时,在此端点返回 200 OK。
-
kubernetes:CoreDNS 将基于服务和 Pod 的 IP 来应答 DNS 查询。 你可以在 CoreDNS 网站找到有关此插件的更多细节。
-
你可以使用
ttl来定制响应的 TTL。默认值是 5 秒钟。TTL 的最小值可以是 0 秒钟, 最大值为 3600 秒。将 TTL 设置为 0 可以禁止对 DNS 记录进行缓存。 -
pods insecure选项是为了与 kube-dns 向后兼容。 -
你可以使用
pods verified选项,该选项使得仅在相同名字空间中存在具有匹配 IP 的 Pod 时才返回 A 记录。 -
如果你不使用 Pod 记录,则可以使用
pods disabled选项。
-
-
prometheus:CoreDNS 的度量指标值以 Prometheus 格式(也称为 OpenMetrics)在
http://localhost:9153/metrics上提供。 -
forward: 不在 Kubernetes 集群域内的任何查询都将转发到预定义的解析器 (/etc/resolv.conf)。
-
cache:启用前端缓存。
-
loop:检测简单的转发环,如果发现死循环,则中止 CoreDNS 进程。
-
reload:允许自动重新加载已更改的 Corefile。 编辑 ConfigMap 配置后,请等待两分钟,以使更改生效。
-
loadbalance:这是一个轮转式 DNS 负载均衡器, 它在应答中随机分配 A、AAAA 和 MX 记录的顺序。
3.3. 配置存根域和上游域名服务器
CoreDNS 能够使用 forward 插件配置存根域和上游域名服务器。
示例:
在 "10.150.0.1" 处运行了 Consul 域服务器, 且所有 Consul 名称都带有后缀 .consul.local。
consul.local:53 {
errors
cache 30
forward . 10.150.0.1
}
要显式强制所有非集群 DNS 查找通过特定的域名服务器(位于 172.16.0.1),可将 forward 指向该域名服务器,而不是 /etc/resolv.conf。
forward . 172.16.0.1
完整示例;
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . 172.16.0.1
cache 30
loop
reload
loadbalance
}
consul.local:53 {
errors
cache 30
forward . 10.150.0.1
}
4. 调试DNS问题
创建一个调试的pod
apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
- name: dnsutils
image: registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3
command:
- sleep
- "infinity"
imagePullPolicy: IfNotPresent
restartPolicy: Always
部署调试pod
4.1. 查看Coredns服务是否正常
kubectl get svc --namespace=kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
...
kube-dns ClusterIP 10.0.0.10 <none> 53/UDP,53/TCP 1h
...
4.2. 查看/etc/resolv.conf
查看容器内dns配置是否符合预期。
kubectl exec -ti dnsutils -- cat /etc/resolv.conf
4.3. nslookup查看解析报错
kubectl exec -i -t dnsutils -- nslookup kubernetes.default
Server: 10.0.0.10
Address 1: 10.0.0.10
Name: kubernetes.default
Address 1: 10.0.0.1
参考:
5.4 - CNI
5.4.1 - CNI接口介绍
1. CNI(Container Network Interface)
CNI(Container Network Interface)即容器网络接口,通过约定统一的容器网络接口,从而kubelet可以通过这个标准的API来调用不同的网络插件实现不同的网络功能。
kubelet启动参数--network-plugin=cni来指定CNI插件,kubelet从--cni-conf-dir (默认是 /etc/cni/net.d) 读取文件并使用 该文件中的 CNI 配置来设置各个 Pod 的网络。 CNI 配置文件必须与 CNI 规约 匹配,并且配置所引用的所有所需的 CNI 插件都应存在于 --cni-bin-dir(默认是 /opt/cni/bin)下。如果有多个CNI配置文件,kubelet 将会使用按文件名的字典顺序排列 的第一个作为配置文件。
CNI规范定义:
-
网络配置文件的格式
-
容器runtime与CNI插件的通信协议
-
基于提供的配置执行网络插件的步骤
-
网络插件调用其他功能插件的步骤
-
插件返回给runtime结果的数据格式
2. CNI配置文件格式
CNI配置文件的格式为JSON格式,配置文件的默认路径:/etc/cni/net.d。插件二进制默认的路径为:/opt/cni/bin。
2.1. 主配置的字段
-
cniVersion(string):CNI规范使用的版本,例如版本为0.4.0。 -
name(string):目标网络的名称。 -
disableCheck(boolean):关闭CHECK操作。 -
plugins(list):CNI插件列表及插件配置。
2.2. 插件配置字段
根据不同的插件,插件配置所需的字段不同。
必选字段:
type(string):节点上插件二进制的名称,比如bridge,sriov,macvlan等。
可选字段:
-
capabilities(dictionary) -
ipMasq(boolean):为目标网络配上Outbound Masquerade(地址伪装),即:由容器内部通过网关向外发送数据包时,对数据包的源IP地址进行修改。当我们的容器以宿主机作为网关时,这个参数是必须要设置的。否则,从容器内部发出的数据包就没有办法通过网关路由到其他网段。因为容器内部的IP地址无法被目标网段识别,所以这些数据包最终会被丢弃掉。
-
ipam(dictionary):IPAM(IP Adderss Management)即IP地址管理,提供了一系列方法用于对IP和路由进行管理。它对应的是由CNI提供的一组标准IPAM插件,比如像host-local,dhcp,static等。比如文中用到的bridge插件,会调用我们所指定的IPAM插件,实现对网络设备IP地址的分配和管理。**如果是自己开发的ipam插件,则相关的入参可以自己定义和实现。以下以host-local为例说明。
- type:指定所用IPAM插件的名称,在我们的例子里,用的是host-local。
- subnet:为目标网络分配网段,包括网络ID和子网掩码,以CIDR形式标记。在我们的例子里为
10.15.10.0/24,也就是目标网段为10.15.10.0,子网掩码为255.255.255.0。 - routes:用于指定路由规则,插件会为我们在容器的路由表里生成相应的规则。其中,dst表示希望到达的目标网段,以CIDR形式标记。gw对应网关的IP地址,也就是要到达目标网段所要经过的“next hop(下一跳)”。如果省略gw的话,那么插件会自动帮我们选择默认网关。在我们的例子里,gw选择的是默认网关,而dst为
0.0.0.0/0则代表“任何网络”,表示数据包将通过默认网关发往任何网络。实际上,这对应的是一条默认路由规则,即:当所有其他路由规则都不匹配时,将选择该路由。 - rangeStart:允许分配的IP地址范围的起始值
- rangeEnd:允许分配的IP地址范围的结束值
- gateway:为网关(也就是我们将要在宿主机上创建的bridge)指定的IP地址。如果省略的话,那么插件会自动从允许分配的IP地址范围内选择起始值作为网关的IP地址。
-
dns(dictionary, optional):dns配置-
nameservers(list of strings, optional) -
domain(string, optional) -
search(list of strings, optional) -
options(list of strings, optional)
-
2.3. 配置文件示例
$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{
"cniVersion": "1.0.0",
"name": "dbnet",
"plugins": [
{
"type": "bridge",
// plugin specific parameters
"bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"],
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1",
"routes": [
{"dst": "0.0.0.0/0"}
]
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
},
{
"type": "tuning",
"capabilities": {
"mac": true
},
"sysctl": {
"net.core.somaxconn": "500"
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
3. CNI插件
3.1. 安装插件
安装CNI二进制插件,插件下载地:https://github.com/containernetworking/plugins/releases
# 下载二进制
wget https://github.com/containernetworking/plugins/releases/download/v1.1.0/cni-plugins-linux-amd64-v1.1.0.tgz
# 解压文件
tar -zvxf cni-plugins-linux-amd64-v1.1.0.tgz -C /opt/cni/bin/
# 查看解压文件
# ll -h
总用量 63M
-rwxr-xr-x 1 root root 3.7M 2月 24 01:01 bandwidth
-rwxr-xr-x 1 root root 4.1M 2月 24 01:01 bridge
-rwxr-xr-x 1 root root 9.3M 2月 24 01:01 dhcp
-rwxr-xr-x 1 root root 4.2M 2月 24 01:01 firewall
-rwxr-xr-x 1 root root 3.7M 2月 24 01:01 host-device
-rwxr-xr-x 1 root root 3.1M 2月 24 01:01 host-local
-rwxr-xr-x 1 root root 3.8M 2月 24 01:01 ipvlan
-rwxr-xr-x 1 root root 3.2M 2月 24 01:01 loopback
-rwxr-xr-x 1 root root 3.8M 2月 24 01:01 macvlan
-rwxr-xr-x 1 root root 3.6M 2月 24 01:01 portmap
-rwxr-xr-x 1 root root 4.0M 2月 24 01:01 ptp
-rwxr-xr-x 1 root root 3.4M 2月 24 01:01 sbr
-rwxr-xr-x 1 root root 2.7M 2月 24 01:01 static
-rwxr-xr-x 1 root root 3.3M 2月 24 01:01 tuning
-rwxr-xr-x 1 root root 3.8M 2月 24 01:01 vlan
-rwxr-xr-x 1 root root 3.4M 2月 24 01:01 vrf
3.2. 插件分类
参考:https://www.cni.dev/plugins/current/
| 分类 | 插件 | 说明 |
|---|---|---|
| main | bridge | Creates a bridge, adds the host and the container to it |
| ipvlan | Adds an ipvlan interface in the container | |
| macvlan | Creates a new MAC address, forwards all traffic to that to the container | |
| ptp | Creates a veth pair | |
| host-device | Moves an already-existing device into a container | |
| vlan | Creates a vlan interface off a master | |
| IPAM | dhcp | Runs a daemon on the host to make DHCP requests on behalf of a container |
| host-local | Maintains a local database of allocated IPs | |
| static | Allocates static IPv4/IPv6 addresses to containers | |
| meta | tuning | Changes sysctl parameters of an existing interface |
| portmap | An iptables-based portmapping plugin. Maps ports from the host’s address space to the container | |
| bandwidth | Allows bandwidth-limiting through use of traffic control tbf (ingress/egress) | |
| sbr | A plugin that configures source based routing for an interface (from which it is chained) | |
| firewall | A firewall plugin which uses iptables or firewalld to add rules to allow traffic to/from the container |
4. CNI插件接口
具体可参考:https://github.com/containernetworking/cni/blob/master/SPEC.md#cni-operations
CNI定义的接口操作有:
ADD:添加容器网络,在容器启动时调用。DEL:删除容器网络,在容器删除时调用。CHECK:检查容器网络是否正常。VERSION:显示插件版本。
这些操作通过CNI_COMMAND环境变量来传递给CNI插件二进制。
其中环境变量包括:
-
CNI_COMMAND:命令操作,包括ADD,DEL,CHECK, orVERSION。 -
CNI_CONTAINERID:容器的ID,有runtime分配,不为空。 -
CNI_NETNS:容器的网络命名空间,命名空间路径,例如:/run/netns/[nsname] -
CNI_IFNAME:容器内的网卡名称。 -
CNI_ARGS:其他参数。 -
CNI_PATH:CNI插件二进制的路径。
4.1. ADD接口:添加容器网络
在容器的网络命名空间CNI_NETNS中创建CNI_IFNAME网卡设备,或者调整网卡配置。
必选参数:
CNI_COMMANDCNI_CONTAINERIDCNI_NETNSCNI_IFNAME
可选参数:
CNI_ARGSCNI_PATH
4.2. DEL接口:删除容器网络
删除容器网络命名空间CNI_NETNS中的容器网卡CNI_IFNAME,或者撤销ADD修改操作。
必选参数:
CNI_COMMANDCNI_CONTAINERIDCNI_IFNAME
可选参数:
CNI_NETNSCNI_ARGSCNI_PATH
4.3. CHECK接口:检查容器网络
必选参数:
CNI_COMMANDCNI_CONTAINERIDCNI_NETNSCNI_IFNAME
可选参数:
CNI_ARGSCNI_PATH
4.4. VERSION接口:输出CNI的版本
参考:
- https://www.cni.dev/docs/spec/
- https://github.com/containernetworking/cni
- https://github.com/containernetworking/cni/blob/spec-v0.4.0/SPEC.md
- https://kubernetes.io/zh/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/
- https://github.com/containernetworking/plugins/tree/master/plugins
- https://www.cni.dev/plugins/current/
- https://cloud.tencent.com/developer/news/600713
- 配置CNI插件
5.4.2 - CNI插件选型
本文主要描述常用的CNI插件的选型,主要包括cilium,calico,flannel三种插件的对比。
1. 技术特点对比
| 特性 | Cilium | Calico | Flannel |
|---|---|---|---|
| 数据面技术 | eBPF 加速 | 基于 iptables(支持 eBPF) | vxlan、host-gw、ipip 等隧道技术 |
| 转发效率 | 高(内核级加速,直通流量) | 高(支持原生路由) | 中(隧道技术增加开销) |
| 可扩展性 | 优(支持高级 L7 策略) | 优(支持原生路由和简单网络策略) | 较低(以 L3 网络为主) |
| 延迟 | 低(无需额外隧道或规则) | 低(无隧道或 eBPF 模式) | 较高(隧道封装增加延迟) |
| 吞吐量 | 高(eBPF 高效转发) | 中(依赖 iptables 或 eBPF) | 较低(隧道开销显著) |
2. 性能指标对比
| 性能指标 | Cilium | Calico | Flannel |
|---|---|---|---|
| 吞吐量 | 高(eBPF 高效转发) | 中-高(取决于模式) | 较低(隧道封装损耗较大) |
| 延迟 | 低(直接路由模式最佳) | 较低(非隧道模式表现良好) | 较高(隧道增加延迟) |
| CPU使用 | 较高(eBPF 和可观测性功能) | 中(iptables/eBPF 开销) | 低(简单架构) |
| 内存使用 | 较高(功能丰富) | 中 | 低 |
3. 测试数据示例
以下是根据典型测试场景总结的指标(单位为吞吐量 Mbps 和延迟 ms):
| 测试场景 | Cilium | Calico | Flannel |
|---|---|---|---|
| 吞吐量 (单节点) | ~9,000 Mbps | ~8,500 Mbps | ~6,000 Mbps |
| 吞吐量 (跨节点) | ~8,000 Mbps | ~7,500 Mbps | ~5,000 Mbps |
| 延迟 (单节点) | ~0.2 ms | ~0.3 ms | ~1.0 ms |
| 延迟 (跨节点) | ~0.4 ms | ~0.5 ms | ~2.0 ms |
2020年测试数据:
数据来源:Benchmark results of Kubernetes network plugins (CNI) over 10Gbit/s network (Updated: August 2020)

[2024年]单位带宽消耗的CPU和内存数据:
数据来源:Benchmark results of Kubernetes network plugins (CNI) over 40Gbit/s network -2024

以上可以看出cilium单位消耗的CPU和内存相比于flannel高。
4. 网络性能分析
4.1. Cilium
- 吞吐量:基于 eBPF 的数据面技术,可直接在 Linux 内核中高效转发流量,减少上下文切换。使用直连路由模式(
--tunnel=disabled)时,进一步减少封装开销。 - 延迟:支持 Sidecar-less 的服务网格架构,能够降低服务间通信延迟。
- 资源消耗:由于其高级功能(如 Hubble 可观测性和 L7 策略),在 CPU 和内存使用上高于其他插件。
4.2. Calico
- 吞吐量:非隧道模式下,基于 BGP 的原生路由性能接近裸机水平;启用 eBPF 模式时,性能进一步提升。
- 延迟:表现良好,但在复杂网络策略下可能增加延迟。
- 资源消耗:资源使用适中,适合大多数生产环境。
4.3. Flannel
- 吞吐量:由于采用 VXLAN、IPIP 等隧道封装方式,其性能通常不如 Cilium 和 Calico。
- 延迟:封装和解封装的额外操作导致延迟增加。
- 资源消耗:架构简单,资源使用最低,适合资源有限的小型集群。
5. 业务场景选型
1. Cilium:适合高性能与安全需求的场景
- 适用场景:
- 微服务架构:Cilium 的 eBPF 技术支持 L7 数据包过滤、服务可观测性和无 Sidecar 服务网格,非常适合复杂微服务环境。
- 边缘计算:在边缘节点上,需要低延迟和高吞吐量,Cilium 的直连路由模式(
--tunnel=disabled)非常高效。 - 多云和混合云:支持多种高级网络功能,如网络策略的灵活配置和透明的加密。
- 局限性:
- 部署复杂度相对较高,对 Linux 内核版本有要求(推荐 5.3+)。
- 资源消耗比 Flannel 高。
2. Calico:适合大规模、灵活策略的企业集群
- 适用场景:
- 大规模 Kubernetes 集群:基于 BGP 的网络路由能够高效扩展,适合公有云和企业级大规模集群。
- 注重安全策略:支持丰富的网络安全策略,并提供对接 eBPF 的能力,兼顾性能和策略灵活性。
- 混合部署:Calico 可以在非 Kubernetes 工作负载中实现一致的网络策略。
- 局限性:
- 默认基于 iptables 的实现在高负载下性能可能不如 Cilium 的 eBPF 数据面。
- 网络策略复杂度较高时,可能增加运维工作量。
3. Flannel:适合轻量级和资源有限的集群
- 适用场景:
- 小型集群:Flannel 架构简单,资源使用少,适合轻量化的 Kubernetes 部署。
- 测试环境:性能需求较低的开发和测试环境中,可以快速搭建和运行。
- 边缘计算(非高性能):对网络性能要求较低的小型边缘节点可以使用。
- 局限性:
- 在吞吐量和延迟上不如 Cilium 和 Calico,尤其是需要大量隧道封装时。
- 缺乏高级网络功能,例如复杂的网络策略和观测能力。
4. 推荐选择总结
| 场景类型 | 推荐插件 | 原因 |
|---|---|---|
| 高性能微服务架构 | Cilium | 提供 eBPF 技术,支持复杂策略和低延迟网络 |
| 大规模企业集群 | Calico | 稳定、灵活,适合多样化和大规模 Kubernetes 部署 |
| 资源受限环境 | Flannel | 简单易用,资源消耗低 |
| 边缘计算 | Cilium/Flannel | Cilium 适合高性能需求,Flannel 适合轻量级节点 |
| 混合云/多云 | Cilium/Calico | Cilium 支持透明加密和现代架构,Calico 提供灵活网络策略支持 |
参考:
- https://docs.cilium.io/en/latest/operations/performance/benchmark/
- https://cilium.io/blog/2018/12/03/cni-performance/
- https://cilium.io/blog/2021/05/11/cni-benchmark/
- Benchmark results of Kubernetes network plugins (CNI) over 40Gbit/s network -2024
- Benchmark results of Kubernetes network plugins (CNI) over 10Gbit/s network (Updated: August 2020)
5.4.3 - Macvlan介绍
1. 简介
macvlan可以看做是物理接口eth(父接口)的子接口,每个macvlan都拥有独立的mac地址,可以被绑定IP作为正常的网卡接口使用。通过这个特性,可以实现在一个物理网络设备绑定多个IP,每个IP拥有独立的mac地址。该特性经常被应用在容器虚拟化中(容器可以配置macvlan的网络,将macvlan interface移动到容器的namespace中)。
示意图:
2. 四种工作模式
2.1. VEPA (Virtual Ethernet Port Aggregator)
VEPA为默认的工作模式,该模式下,所有macvlan发出的流量都会经过父接口,不管目的地是否与该macvlan共用一个父接口。
2.2. Bridge mode
该bridge模式类似于传统的网桥模式,拥有相同父接口的macvlan可以直接进行通信,不需要将数据发给父接口转发。该模式下不需要交换机支持hairpin模式,性能比VEPA模式好。另外相对于传统的网桥模式,该模式不需要学习mac地址,不需要STP,使得其性能比传统的网桥性能好得多。但是如果父接口down掉,则所有子接口也会down,同时无法通信。
2.3. Private mode
该模式是VEPA模式的增强版,
2.4. Passthru mode
.
待完善
参考:
- https://backreference.org/2014/03/20/some-notes-on-macvlanmacvtap/
- https://github.com/containernetworking/plugins/tree/master/plugins/main/macvlan
- https://github.com/containernetworking/plugins/blob/master/plugins/main/macvlan/macvlan.go
- http://wikibon.org/wiki/v/Edge_Virtual_Bridging
- Linux 虚拟网卡技术:Macvlan
- http://hicu.be/bridge-vs-macvlan
- http://hicu.be/docker-networking-macvlan-bridge-mode-configuration
5.5 - Flannel
5.5.1 - Flannel介绍
1. Flannel简介
Flannel 是一个简单的、易于使用的 Kubernetes 网络插件,用于为容器集群提供网络功能。它主要解决的是 Kubernetes 集群中跨节点容器间通信的问题,通过为每个节点分配一个独立的子网,确保容器之间可以使用虚拟网络进行无障碍通信。
1.1. Flannel 的特点与优势
易于配置和使用- 提供简单的配置文件,易于集成到 Kubernetes 集群中。
- 支持多种后端(如 VXLAN、host-gw、AWS VPC 等),灵活满足不同环境需求。
跨节点网络通信- 为每个节点分配独立的子网,容器之间使用虚拟网络 IP 直接通信,而无需 NAT。
轻量级设计- 运行时资源占用少,适合资源有限的环境。
稳定兼容性强- 支持多种 Linux 发行版,兼容 Kubernetes 和 Docker,适应广泛的容器化场景。
多后端支持- 提供 VXLAN、host-gw、AWS VPC、UDP 等多种网络后端,以适应不同场景和需求。
1.2. 使用场景
- 中小规模 Kubernetes 集群
- Flannel 易于部署和管理,非常适合中小规模集群使用。
- 跨节点容器通信
- 在需要容器间无障碍通信的场景中,Flannel 提供可靠的虚拟网络支持。
- 非高性能敏感的场景
- 由于 Flannel 使用封包封装技术(如 VXLAN),在性能要求不是特别高的场景中非常适用。
- 混合云/多云部署
- Flannel 的多后端支持和灵活配置,使其在多种基础设施中易于部署。
1.3. Flannel 的局限性
尽管 Flannel 易用且轻量,但它也存在一些不足之处:
- 性能限制
- 使用 VXLAN 或 UDP 后端时,由于封包和解封包操作会消耗额外资源,网络性能可能不如直接路由的方案(如 Calico 的 BGP)。
- 在高流量或低延迟场景下,Flannel 可能不是最佳选择。
- 缺乏高级网络功能
- 不支持网络策略(Network Policy)功能,无法实现细粒度的访问控制。
- 对于需要复杂网络功能(如流量加密、多租户隔离)的场景,Calico 或 Cilium 是更好的选择。
- 依赖 etcd
- Flannel 强依赖于 etcd。如果 etcd 出现故障,可能影响网络管理和子网分配。
- 需要额外注意 etcd 的高可用性和性能。
- 运维复杂度随着规模增长
随着集群规模扩大(如 1000+ 节点),Flannel 的资源消耗和配置复杂度可能增加,不如更高性能的网络方案。
2. Flannel 的架构与配置
flannel的架构比较简单,只有每个节点一个的flanneld组件,通过daemonset部署,并没有跟calico或cilium的架构中有中控组件。其他组件则使用k8s的etcd。
- flanneld 组件
- 每个节点运行一个 flanneld 服务,负责管理该节点的网络配置和数据封包解封包。
- etcd 集成
- Flannel 使用 etcd 存储网络配置和子网分配信息。
- 所有节点通过 etcd 协调分配网络资源。
- 网络后端
- Flannel 支持多种后端技术,如 VXLAN、UDP、host-gw 等,可根据需求选择。
- Kubernetes 集成
- Flannel 通过 Kubernetes 的 CNI 插件接口无缝集成,确保与 Kubernetes 网络需求的兼容性。
2.1. 部署flannel
通过以下的yaml文件可以快速的部署flannel组件
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
部署完成后会生成默认配置:kube-flannel-cfg, 其中默认使用vxlan的后端模式。
apiVersion: v1
kind: ConfigMap
data:
cni-conf.json: |
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan" // 默认为vxlan的模式
}
}
2.2. 节点配置
在/etc/cni/net.d路径下会生成flannel的cni配置。
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
同时在节点会生成一个子网配置/var/run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16 # 整个集群的Pod网段
FLANNEL_SUBNET=10.244.3.1/24 # 该节点的子网Pod网段
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
节点会生成cni0和flannel.1的网卡,其中网卡的网段跟该节点的FLANNEL_SUBNET网段一致,如果不一致则需要重建网卡。
flannel.1:节点网关,10.244.3.0cni0: 10.244.3.1FLANNEL_SUBNET=10.244.3.1/24
cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.3.1 netmask 255.255.255.0 broadcast 10.244.3.255
inet6 fe80::828:abff:fe83:34ac prefixlen 64 scopeid 0x20<link>
ether 0a:28:ab:83:34:ac txqueuelen 1000 (Ethernet)
RX packets 16220840959 bytes 2329280828193 (2.3 TB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 16382181068 bytes 43297103465563 (43.2 TB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.3.0 netmask 255.255.255.255 broadcast 0.0.0.0
inet6 fe80::d875:a3ff:fe8b:1e64 prefixlen 64 scopeid 0x20<link>
ether da:75:a3:8b:1e:64 txqueuelen 0 (Ethernet)
RX packets 225837271 bytes 33216374045 (33.2 GB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 204036495 bytes 396891087255 (396.8 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
3. Flannel网络原理
3.1. 原理图

图中docker0等价于cni0, flannel0等价于flannel.1网卡
关键网卡和路由表的角色
cni0网桥- 是一个 Linux 网桥,连接同一节点上的所有 Pod 网络接口(
veth对)。 将数据包从 Pod 转发到本地的其他 Pod或上交给flannel.1。
- 是一个 Linux 网桥,连接同一节点上的所有 Pod 网络接口(
flannel.1网卡- 是一个虚拟网卡(VXLAN 接口),
用于封装和解封装跨节点的 Pod 数据包。 - 连接到物理网络,通过封装的方式将数据包发送到目标节点。
- 是一个虚拟网卡(VXLAN 接口),
路由表- 定义了如何转发数据包,包括 Pod 子网的路由规则和默认路由。
- Flannel 会在每个节点配置路由表,使得本地 Pod 的子网可以通过
cni0访问,远程 Pod 的子网通过flannel.1访问。
3.2. 数据包路径
3.2.1. 数据包环境
假设有以下环境:
- Node A:子网
10.244.1.0/24,Pod1 的 IP 是10.244.1.2。 - Node B:子网
10.244.2.0/24,Pod2 的 IP 是10.244.2.3。 - 两个节点通过物理网络互联。
其中节点路由表信息如下:
A 节点(Pod IP:10.244.1.2):
# 物理机路由
default via <物理机网关> dev bond0 proto static
<物理机目标网段> dev bond0 proto kernel scope link src <本机节点IP>
# flannel路由
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1 #本地子网,直接通过 cni0 处理。
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink # 远程子网,数据包通过 flannel.1 封装
B节点(Pod IP:10.244.2.3):
# 物理机路由
default via <物理机网关> dev bond0 proto static
<物理机目标网段> dev bond0 proto kernel scope link src <本机节点IP>
# flannel路由
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink # 远程子网,数据包通过 flannel.1 封装
10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1 #本地子网,直接通过 cni0 处理。
3.2.1. 数据路径
1. 从 Pod 发出的数据包
(1) Pod1 发送数据包
- Pod1 发往 Pod2 的数据包:
- 源 IP:
10.244.1.2 - 目标 IP:
10.244.2.3
- 源 IP:
- 数据包通过 Pod1 的
veth设备发送到本地的cni0网桥。
2. cni0 网桥处理
- 判断目标 IP 属于哪个子网:
- 本节点子网(
10.244.1.0/24):直接转发到对应的veth。 - 其他子网(
10.244.2.0/24):路由表指向flannel.1。
- 本节点子网(
- 在本例中,目标 IP 属于
10.244.2.0/24,因此数据包通过路由规则转发到flannel.1。
3. flannel.1 网卡封装
(1) 数据封装
- Flannel 代理(
flanneld)会检测目标子网属于远程节点,触发封装流程。 - 数据包封装为 VXLAN 包,外层 IP 标头:
- 源 IP:Node A 的物理 IP(例如
192.168.1.1)。 - 目标 IP:Node B 的物理 IP(例如
192.168.1.2)。 - VXLAN Header:标记虚拟网络 ID 和其他信息。
- 源 IP:Node A 的物理 IP(例如
(2) 路由转发
- 封装后的数据包通过主机的物理网卡(如
eth0)发送到目标节点。
4. 到达目标节点 (Node B)
(1) flannel.1 接收数据包
- Node B 的物理网卡接收封装的 VXLAN 数据包,交由
flannel.1。 - flannel.1 解封数据包,还原出原始的 Pod 数据包:
- 源 IP:
10.244.1.2 - 目标 IP:
10.244.2.3
- 源 IP:
(2) 路由到 cni0
- 根据路由表,目标子网
10.244.2.0/24属于本节点,通过cni0转发数据包。 cni0根据目标 IP,找到 Pod2 的veth设备。
5. 数据包到达目标 Pod
- 数据包最终通过
cni0网桥送到 Pod2 的veth接口,Pod2 接收到来自 Pod1 的通信。
3.2.3. 数据流总结
- Pod1 数据包先进入本地的
cni0网桥。 cni0网桥通过路由表,发现目标 IP 属于其他子网,交由flannel.1。flannel.1封装数据包,并通过物理网卡发往目标节点。- 目标节点的
flannel.1解封数据包,交给cni0。 cni0网桥将数据包转发到目标 Pod 的veth。
通过这种方式,不同节点的 Pod 实现了透明的互通。
4. 总结
Flannel是一个非常简单,稳定的CNI插件,其中部署和配置方式都非常简单,网络原理也简单,出现问题排查比较方便。特别适合k8s集群规模不大(1000个节点以内),网络性能要求不是非常严格,且团队中网络相关人员较少且无法支持维护复杂网络插件的团队使用。因为选择方案有一个基本的考虑点是该方案稳定且团队中有人可维护,而Flannel是一个维护成本相对比较低的网络方案。
参考:
5.5.2 - VXLAN原理介绍
1. VXLAN简介
VXLAN(Virtual Extensible LAN)是一种网络虚拟化技术,旨在解决传统二层网络扩展的局限性,尤其是在数据中心大规模部署中。它通过隧道技术将二层以太网帧封装在三层UDP包中,实现了跨三层网络的二层网络延展。
1.1. VXLAN的基本概念
- 目的:解决二层网络扩展的问题,例如VLAN的数量限制(传统VLAN ID 只能支持4096(2的12次方)个)。
- 封装协议:VXLAN将二层以太网帧封装为UDP数据包(即VXLAN隧道)。
- VXLAN网络标识:
- VXLAN使用24位的VXLAN网络标识(VNI,Virtual Network Identifier),支持多达16,777,216(2的24次方)个虚拟网络。
- 每个VNI对应一个虚拟的二层广播域(类似于传统的VLAN)。
1.2.VXLAN应用场景
- 多租户数据中心:为不同租户提供逻辑隔离的虚拟网络。
- 混合云和跨数据中心连接:扩展二层网络到不同位置的数据中心。
容器网络:在Kubernetes等平台上,用VXLAN构建跨节点的Pod网络。
1.3. VXLAN的优点
- 可扩展性:
- 支持大量虚拟网络(16M)。
- 跨三层网络扩展二层网络,适用于大规模数据中心。
- 网络隔离:通过VNI实现网络隔离,适合多租户场景。
- 灵活性:VXLAN在IP网络中运行,不依赖底层的物理拓扑。
1.4. VXLAN的局限性
- 性能开销:封装和解封装增加了CPU负载,尤其是在软件实现中。
- 复杂性:需要额外配置VTEP和三层网络,维护成本较高。
- MTU问题:VXLAN封装增加了数据包长度,可能需要调整网络的MTU(通常为1600字节或更大)。
2. VXLAN的原理
2.1. VXLAN的关键组成
VTEP(VXLAN Tunnel Endpoint):- VTEP是VXLAN隧道的起点和终点,用于封装和解封装VXLAN数据包。
- 通常运行在物理交换机或虚拟机主机的网卡上。
- 包括两个接口:
- 本地接口:连接到二层网络。
- 隧道接口:连接到三层网络。
VXLAN头:- VXLAN头插入到原始以太网帧和UDP头之间。
- VXLAN头包含VNI等信息,用于区分不同的虚拟网络。
UDP头:- VXLAN数据包封装在UDP中,以便通过三层网络传输。
默认使用UDP端口号4789。确保iptables规则中该UDP端口是放开的。
2.2. VXLAN的工作原理
VXLAN通过以下步骤实现跨三层网络的二层通信:
1. 数据包封装
- VTEP捕获本地虚拟机(VM)的以太网帧。
- VTEP在帧上封装:
- 添加VXLAN头,用于标识VNI。
添加UDP头,便于三层网络传输。- 添加外层IP头和MAC头,用于在三层网络中寻址。
2. 数据包传输
封装后的数据包通过三层网络传输到目标VTEP。传输过程中使用三层网络的路由功能,可以跨越不同的子网。
3. 数据包解封装
- 目标VTEP接收到VXLAN数据包后,解析外层IP头。
- 检查VNI,将数据包解封装回原始二层以太网帧。
- 将解封装后的帧转发到目标虚拟机。
3. VLAN 和 VXLAN 的区别
| 特性 | VLAN | VXLAN |
|---|---|---|
| 定义 | 二层网络分段技术,通过 802.1Q 标准实现。 | 二层覆盖网络技术,通过三层网络扩展二层网络。 |
| 标准 | IEEE 802.1Q | IETF RFC 7348 |
| 隔离方式 | 通过 12 位 VLAN ID 标记帧,实现二层广播域隔离。 | 通过 24 位 VXLAN ID(VNI)实现隔离。 |
| 支持网络数量 | 最多 4096 个 VLAN | 超过 1600 万个虚拟网络 |
| 封装方式 | 在以太网帧中添加 4 字节 VLAN Tag。 | 在以太网帧外封装 UDP,增加 IP 和 VXLAN 标头。 |
| 网络边界 | 限于局域网,依赖物理拓扑。 | 基于三层网络,支持跨地域、跨数据中心连接。 |
| 性能 | 性能高,硬件交换机对 VLAN 支持成熟。 | 封装和解封装增加开销,但灵活性更强。 |
| 适用场景 | 中小型网络,局域网内的简单隔离需求。 | 大规模云环境,多租户数据中心,跨地域网络。 |
| 复杂性 | 配置简单,维护容易。 | 配置复杂,需要支持 VXLAN 的设备。 |
| 硬件依赖 | 广泛支持,几乎所有交换机都支持。 | 需要支持 VXLAN 的设备或软件实现。 |
| 广播域扩展 | 广播域较大,不适合大规模网络。 | 通过三层网络扩展二层广播域。 |
4. Flannel VXLAN 的基本原理
Flannel 是 Kubernetes 中常用的网络插件之一,用于实现容器跨节点的网络通信。它支持多种网络后端,其中 VXLAN 后端 是一种常用的选择,利用 VXLAN 隧道实现不同节点的容器网络互通。
Flannel 使用 VXLAN 创建一个虚拟的二层网络,把位于不同节点上的容器子网连接起来。这些子网统一组成一个逻辑上的扁平网络,使得容器可以使用 Pod IP 直接互通。
在 VXLAN 模式下:
- 每个节点分配一个独立的子网(例如
/24),该子网中的 IP 地址分配给该节点上的 Pod。 - VXLAN 隧道用于在不同节点之间封装和传输数据包。
4.1. Flannel VXLAN 的关键组件
- etcd / Kubernetes API:
- Flannel 使用 etcd 或 Kubernetes API 作为存储,保存每个节点的子网分配信息。
- 例如,节点 A 的子网是
10.1.1.0/24,节点 B 的子网是10.1.2.0/24。
- flanneld 进程:
- 每个节点运行 flanneld,负责:
- 从 etcd 获取子网信息。
- 配置 VXLAN 设备。
- 管理路由规则。
- 每个节点运行 flanneld,负责:
- VXLAN 设备:
Flannel 在每个节点上创建一个 VXLAN 虚拟网络接口(如flannel.1)。- 通过这个接口,将数据包封装到 VXLAN 隧道中。
4.2. Flannel VXLAN 的通信流程
4.2.1. Pod 到 Pod 通信示例
假设 Pod1 在节点 A,Pod2 在节点 B,Pod1 的 IP 为 10.1.1.2,Pod2 的 IP 为 10.1.2.3:
- 数据包生成:
- Pod1 想要与 Pod2 通信,发送一个 IP 数据包,目标地址是
10.1.2.3。
- Pod1 想要与 Pod2 通信,发送一个 IP 数据包,目标地址是
- 节点路由查找:
- 节点 A 的路由表根据目标 IP (
10.1.2.3),发现其子网10.1.2.0/24属于节点 B。 - 数据包被转发到 VXLAN 设备
flannel.1。
- 节点 A 的路由表根据目标 IP (
- VXLAN 封装:
- 在 flannel.1上,数据包被封装:
- 原始 IP 数据包被作为 VXLAN 的有效载荷。
- VXLAN 头部和外层 UDP/IP 头部被添加。
- 外层 IP 头的目标地址是节点 B 的物理 IP 地址。
- 在 flannel.1上,数据包被封装:
- 跨网络传输:
- 封装后的数据包通过底层三层网络(通常是宿主机的物理网卡)发送到节点 B。
- 解封装和转发:
- 节点 B 的 VXLAN 设备接收到数据包后,解封装外层头部,还原出原始 IP 数据包。
- 节点 B 根据路由规则将数据包转发给 Pod2。
4.2.2. 路由和 ARP 的处理
-
路由表:
-
Flannel 会在每个节点配置路由规则,将目标子网与相应的 VXLAN 隧道设备关联。
-
例如:
-
10.1.2.0/24 via 192.168.1.2 dev flannel.1
-
ARP 处理:
- VXLAN 需要知道远程节点的物理 IP 地址。
- Flannel 在 VXLAN 模式下通过 etcd 或 Kubernetes API 维护节点的 IP 映射关系,而不是依赖传统的 ARP。
5. Flannel VXLAN报文解析
通过vxlan隧道传输需要封vxlan包和解vxlan包,以下描述vxlan报文内容。

5.1. VXLAN 报文结构
Flannel 的 VXLAN 报文可以分为以下几个主要部分:
| 字段 | 描述 |
|---|---|
| 外层以太网头 | 用于传输 VXLAN 报文的物理网络的 MAC 地址。 |
| 外层 IP 头 | 用于三层传输,包含源 IP(本地物理机)和目的 IP(目标物理机)。 |
| UDP 头 | 用于封装 VXLAN 流量,通常使用 VXLAN 的默认端口 4789。 |
| VXLAN 标头 | 包含 VXLAN 的关键信息,例如 VNI(虚拟网络标识)。 |
| 内层以太网头 | 原始的以太网帧头,用于容器或 Pod 之间通信。 |
| 内层数据(Payload) | 实际的应用层数据,例如 HTTP、TCP 或 ICMP 数据。 |
示例:
+-----------------------------+
| 外层以太网头 | MAC 源地址 | MAC 目的地址 | 类型 |
+-----------------------------+
| 外层 IP 头 | 源 IP 地址 | 目的 IP 地址 | 协议|
+-----------------------------+
| UDP 头 | 源端口 | 目的端口 | 长度 |
+-----------------------------+
| VXLAN 标头 | Flag | Reserved | VNI |
+-----------------------------+
| 内层以太网头 | 源 MAC 地址 | 目的 MAC 地址 |
+-----------------------------+
| 内层数据(Payload) | 实际的数据内容 |
+-----------------------------+
5.2. 报文详细解析
1. 外层以太网头
- 作用:用于承载 VXLAN 报文的实际网络传输。
- 内容:
- 源 MAC 地址:发送报文的物理网卡的 MAC 地址。
- 目的 MAC 地址:目标主机的物理网卡的 MAC 地址。
2. 外层 IP 头
- 作用:在三层网络中将 VXLAN 报文路由到目标主机。
- 内容:
- 源 IP 地址:发送报文的物理主机的 IP 地址。
- 目的 IP 地址:目标主机的 IP 地址。
- 协议类型:UDP。
3. UDP 头
- 作用:封装 VXLAN 数据。
- 内容:
- 源端口:动态分配的随机端口。
- 目的端口:VXLAN 的端口,例如
8472(可以在 Flannel 配置中自定义)。 - 长度和校验和:用于确保 UDP 报文的完整性。
4. VXLAN 标头
- 作用:标识虚拟网络以及对 VXLAN 流量进行必要的控制。
- 格式:
- Flag:8 位,标识是否启用 VXLAN 功能,通常为
0x08。 - VNI(Virtual Network Identifier):24 位,标识 VXLAN 所属的虚拟网络。
- Reserved:用于对齐和扩展,通常为 0。
- Flag:8 位,标识是否启用 VXLAN 功能,通常为
5. 内层以太网头
- 作用:封装原始的以太网帧,用于容器或 Pod 之间的二层通信。
- 内容:
- 源 MAC 地址:发送容器或 Pod 的 MAC 地址。
- 目的 MAC 地址:目标容器或 Pod 的 MAC 地址。
6. 内层数据(Payload)
- 作用:实际的用户数据。
- 内容:
- 数据类型:可以是 IP 数据包(例如 TCP、UDP 或 ICMP),也可能是 ARP 等协议。
5.3. 报文的捕获
可以通过命令ip -d link show flannel.1查看vxlan id(VNI) ,vxlan端口,vxlan基于的物理网卡。
例如,以下查询到vxlan id(VNI)为1,vxlan端口为8472,基于的物理网卡是bond0。
# ip -d link show flannel.1
10: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether xxxxxx brd xxxxx promiscuity 0 minmtu 68 maxmtu 65535
vxlan id 1 local xxxxxxx dev bond0 srcport 0 0 dstport 8472 nolearning ttl auto ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
使用 tcpdump,在物理网卡上捕获 VXLAN 报文, 协议是UDP,添加vxlan的端口。
tcpdump -i bond0 udp port 8472 -vv
6. 如何避免VXLAN冲突
在同一台物理机上如果存在其他设备(例如:弹性外网EIP设备)使用了VXLAN的隧道技术,可能与Flannel的VXLAN设备存在冲突,以下是冲突的可能性、原因及解决方法:
6.1. 可能存在的冲突
1. UDP 端口冲突
- VXLAN 通常使用 UDP 端口 4789 进行封装传输。如果弹性外网 IP 的 VXLAN 实现和 Flannel (默认端口8472)都使用相同的 UDP 端口,那么会导致端口冲突,使得其中一个功能失效。
2. VNI (VXLAN Network Identifier) 冲突
- VXLAN 的每个虚拟网络通过 VNI(24 位)进行区分。
- 如果弹性外网 IP 和 Flannel 的 VXLAN 使用了相同的 VNI,则可能导致 VXLAN 隧道之间的隔离性失效,造成数据包混乱。
3. 路由或设备名冲突
- 两者都会在物理机上创建 VXLAN 设备(如
vxlan0或flannel.1),如果设备名称相同,可能导致配置混乱。 - 路由表可能同时存在与 VXLAN 隧道相关的规则,如果路由目标网络有重叠,可能导致流量被错误转发。
6.2. 冲突的解决方法
1. 避免 UDP 端口冲突
- 确认弹性外网 IP 的 VXLAN 端口号。
- 自定义Flannel的VXLAN 端口号。确保两者使用不同的端口号。
2. 避免 VNI 冲突
- 确认弹性外网 IP 的 VXLAN 实现是否允许指定 VNI。如果允许,则为两者设置不同的 VNI 范围。
- Flannel VNI 通常由其内部管理,自动分配,但可以在配置文件中指定范围或固定值。
3. 避免设备名冲突
- Flannel 默认设备名是
flannel.1,一般非flannel的设备不会设置为该名称。 - 确保弹性外网 IP 的 VXLAN 设备名不同,或显式指定设备名称。
4. 路由隔离
仔细检查路由表,确保两者的目标子网范围没有重叠。
如果要修改flannel的冲突参数,可以修改配置文件。
kubectl edit cm -n kube-flannel kube-flannel-cfg
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan",
"VNI": 1, # 默认值
"Port": 8472 # 默认值
}
}
6.3. 查看设备的VXLAN
可以使用以下命令列出所有 VXLAN 接口:
# ip link show type vxlan
# 输出
6: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 ...
7: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 ...
可以查看 VXLAN 接口的配置信息:
# ip -d link show <vxlan_interface>
6: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 06:42:ae:ff:fe:90 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 42 local 192.168.1.10 dev eth0 port 4789 0
...
# vxlan id 42:表示 VXLAN 的 VNI(Virtual Network Identifier)是 42。
# port 4789:表示 VXLAN 使用的 UDP 端口是 4789(默认端口)。
如果 VXLAN 接口绑定到了桥接设备(bridge),可以通过 bridge 命令查询详细信息。
bridge fdb show dev <vxlan_interface>
# 输出
00:00:00:00:00:13 dst x.x.x.x self permanent
7. 总结
本文主要介绍了VXLAN的基本概念和原理,报文解析以及在Flannel中的使用。除了Flannel外,在Calico和Cilium的网络插件中也涉及到VXLAN的使用,基本原理类似,大同小异。通过本文的介绍,帮助读者对于其他场景下使用VXLAN的方式也能够快速理解,并且可以快速排查VXLAN相关的网络问题。
参考:
5.6 - Calico
5.6.1 - Calico介绍
1. Calico简介
Calico 是一个开源的网络和网络安全解决方案,主要用于 Kubernetes 等容器编排系统。它通过提供高效的网络连接和强大的安全控制来满足容器化和微服务架构的需求。Calico 以其灵活性、可扩展性和性能著称,是许多企业和云原生应用的首选网络插件。
1.1. 主要功能和特点
-
网络架构:
- L3 路由架构:Calico 基于第三层(L3)网络构建,不依赖传统的覆盖网络(overlay network),使其可以利用 IP 路由来实现跨节点的直接通信。
- BGP 支持:Calico 使用 BGP(边界网关协议)来分发和同步路由信息,使得容器和 Pod 能够跨节点直接通信,减少网络延迟,提升性能。
- 支持多种后端:除了默认的 IP 路由模式,Calico 也支持 VXLAN、IPIP、WireGuard 和 eBPF 后端,适应不同的网络环境和需求。
-
网络安全:
- 网络策略:Calico 支持标准的 Kubernetes NetworkPolicy,用户可以通过策略控制 Pod 之间的通信权限。
- GlobalNetworkPolicy:Calico 提供 GlobalNetworkPolicy,可以实现跨命名空间、跨集群的统一策略管理,用于多租户隔离或更高的安全控制。
- 支持 eBPF:Calico 在启用 eBPF 模式时,可以通过 eBPF 提供更高效的网络数据处理,并支持 L4 层的负载均衡和网络策略控制。
-
可观测性和可视化:
- Calico 集成了多种可观测性工具,支持 Prometheus 等监控系统,并可以生成网络流量和策略的监控指标,帮助运维人员了解网络状况。
- 支持与 EFK(Elasticsearch, Fluentd, Kibana)集成,便于可视化网络流量和策略。
-
高扩展性:
- Calico 可以在不同的基础设施中运行,包括本地数据中心和云平台(AWS、GCP、Azure),并支持与 VM 及裸金属服务器互联。
- 使用 IP 路由和 BGP,使得 Calico 在大规模集群中也能保持良好的性能和扩展性。
1.2. 适用场景
- 容器化应用的网络管理:适用于 Kubernetes 等容器编排平台,为容器提供高效、安全的网络连接。
- 混合云和多集群部署:Calico 支持跨数据中心和云环境的多集群部署,非常适合混合云和多租户环境。
- 网络安全:对于需要细粒度的网络安全策略控制和多租户隔离的场景,Calico 提供多层次的安全控制和隔离。
1.3. Calico的局限性
calico也存在一些不足和局限性。
1. 对网络拓扑的依赖
- BGP 配置复杂性: Calico 默认使用 BGP 协议进行路由,如果网络环境中没有现成的 BGP 支持(如非生产环境或网络管理员经验不足),配置可能较复杂。此外,BGP 的管理对部分运维人员有一定门槛。
- 对底层网络要求较高: Calico 的无隧道设计依赖底层网络的正常运行。如果底层网络不支持高效的路由或不能很好地管理多播流量,可能会影响整体网络性能。
2. 大规模集群性能问题
- etcd 负载: 在大规模 Kubernetes 集群中(如几千个节点),Calico 对 etcd 的访问频率较高,这可能会给 etcd 带来较大压力。尽管 Typha 可以缓解部分问题,但依然可能成为瓶颈。
- 路由表膨胀: 在大规模集群中,BGP 模式下每个节点需要维护大量路由信息(与集群中 Pod 的数量相关),可能导致路由表膨胀并对内存和 CPU 资源产生较大压力。
2. Calico架构图
来自官网
2.1. Calico 架构概览
Calico 通过 BGP(边界网关协议)在 Kubernetes 集群节点之间进行路由广播,无需使用复杂的隧道协议(如 VXLAN 或 GRE),这使得 Calico 的网络性能较高。它主要由以下组件组成:
- calico-node:这是 Calico 的核心组件,运行在每个节点上,负责设置路由、管理 IP 地址,并通过 Felix 实现网络策略的执行。
- BGP Daemon (Bird):负责节点之间的路由传播,以实现不同节点 Pod 之间的流量路由。
- Felix:作为 Calico 的主代理,负责监控 etcd 中存储的网络策略并将其应用于节点的网络接口,同时负责设置 IP 路由、管理 ACLs 以确保流量符合策略。
- Typha:当节点数较多时,可以通过 Typha 组件来减少 etcd 的访问负载。它会将 etcd 中的策略变化缓存起来,并同步给每个节点的 Felix 代理。
- etcd:Calico 使用 etcd 作为存储后端,用于存储网络策略、IP 池等配置。也可以使用 Kubernetes 的 API Server 作为存储后端,便于集成。
2.2. Calico 的核心组件
- calicoctl:这是 Calico 提供的命令行工具,用于配置和管理 Calico 的资源,比如 IP 池、策略、网络设置等。可以通过该工具查看、创建、更新和删除网络策略。
- IPAM (IP Address Management):Calico 自带的 IP 地址管理模块,可以为集群中的 Pod 自动分配 IP 地址。IPAM 支持灵活的 IP 池设置,可以对不同的节点、命名空间或工作负载分配特定的 IP 范围。
2.3. 工作流程
- 每个节点上运行的 calico-node 组件会和其他节点进行 BGP 路由信息交换,确保不同节点的 Pod 可以互相通信。
- Felix 组件负责将网络策略的定义应用到实际的网络接口上,以确保流量符合预设的策略。
- Typha 组件在大规模集群中可以有效地减少 etcd 的压力,帮助 Felix 快速同步网络策略。
2.4. Calico 网络策略 (Network Policy)
Calico 支持丰富的网络策略,用于定义不同的 Pod 或服务之间的网络访问规则。网络策略的主要功能包括:
- 基于标签的策略:可以根据 Pod 或命名空间的标签来控制网络流量的允许和拒绝。
- 支持 Egress 和 Ingress 策略:不仅可以控制进入 Pod 的流量,还可以控制 Pod 发出的流量。
- 灵活的规则定义:支持基于 IP 地址、端口、协议的规则配置,能够精细地控制网络流量。
3. Calico组件及配置
calico的部署可参考:kubeadm-scripts/cni/install-calico.sh
部署完成后可以在k8s集群中看到以下组件:
-
中控组件:calico-kube-controllers
-
节点组件:calico-node
calico-system calico-kube-controllers-8945657f7-ntbxm 1/1 Running 0 421d
calico-system calico-node-2df8c 1/1 Running 0 421d
calico-system calico-node-5vq6z 1/1 Running 0 421d
calico-system calico-node-dpnkd 1/1 Running 0 421d
calico-system calico-node-sms2h 1/1 Running 0 421d
calico-system calico-node-w95l2 1/1 Running 0 414d
3.1. calico node进程树
\_ /usr/local/bin/runsvdir -P /etc/service/enabled
\_ runsv confd
| \_ calico-node -confd
\_ runsv allocate-tunnel-addrs
| \_ calico-node -allocate-tunnel-addrs
\_ runsv monitor-addresses
| \_ calico-node -monitor-addresses
\_ runsv bird
| \_ bird -R -s /var/run/calico/bird.ctl -d -c /etc/calico/confd/config/bird.cfg
\_ runsv felix
| \_ calico-node -felix
\_ runsv cni
| \_ calico-node -monitor-token
\_ runsv node-status-reporter
| \_ calico-node -status-reporter
\_ runsv bird6
\_ bird6 -R -s /var/run/calico/bird6.ctl -d -c /etc/calico/confd/config/bird6.cfg
3.2. calico-kube-controllers进程树
/usr/bin/kube-controllers
3.3. CNI calico二进制
cd /opt/cni/bin
|-- calico
|-- calico-ipam
|-- install
3.4. CNI calico配置
10-calico.conflist
cd /etc/cni/net.d
# cat 10-calico.conflist
{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "calico",
"log_level": "info",
"log_file_path": "/var/log/calico/cni/cni.log",
"datastore_type": "kubernetes",
"nodename": "node1",
"mtu": 0,
"ipam": {
"type": "calico-ipam"
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
}
},
{
"type": "portmap",
"snat": true,
"capabilities": {"portMappings": true}
},
{
"type": "bandwidth",
"capabilities": {"bandwidth": true}
}
]
}
calico-kubeconfig
# Kubeconfig file for Calico CNI plugin. Installed by calico/node.
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
server: https://10.96.0.1:443
certificate-authority-data: "xxx"
users:
- name: calico
user:
token:xxx
contexts:
- name: calico-context
context:
cluster: local
user: calico
参考:
5.7 - Cilium
5.7.1 - Cilium介绍
1. Cilium简介
Cilium 是一个开源的容器网络插件(CNI),专为 Kubernetes 和云原生环境设计,基于 eBPF(Extended Berkeley Packet Filter) 实现高性能、可扩展的网络和安全功能。它支持微服务间的细粒度流量控制,能够在 L3/L4/L7 层提供网络策略,同时具有强大的可观测性工具(如 Hubble)以帮助运维人员监控和优化流量。
1.1. 核心特性
-
基于 eBPF 的高性能数据平面
-
eBPF:Cilium 通过 eBPF 在 Linux 内核中直接运行数据包处理逻辑,避免了内核与用户态的频繁切换,大幅提高了性能。
-
高效流量转发:支持 BPF 的快速路径优化(Zero-Copy 转发),在高流量环境中表现出色。
-
-
多层网络策略
-
L3/L4 策略:基于 IP 和端口的基本流量控制。
-
L7 策略:支持应用层协议(如 HTTP、gRPC)的访问控制,可以根据请求路径、方法或内容过滤流量。
-
微服务友好:特别适合需要细粒度网络策略的微服务架构。
-
-
可观测性
-
Hubble:Cilium 内置的可观测性平台,可以实时监控服务间的网络流量、延迟和错误率,帮助开发和运维团队快速定位问题。
-
流量路径追踪:支持流量路径的全链路追踪,便于排查网络瓶颈或策略冲突。
-
-
拓展性
-
支持自定义 eBPF 程序,用户可以根据业务需求扩展网络功能。
-
与其他云原生工具(如 Prometheus、Grafana、Istio)无缝集成。
-
-
跨云和混合云支持
- 支持 Kubernetes 集群的多网络环境,例如在跨云和混合云场景中提供统一的网络策略和流量控制。
-
服务发现与负载均衡
-
内置服务负载均衡:提供内核级的流量负载均衡,比传统的 kube-proxy 性能更高。
-
服务发现支持:可以与 Kubernetes 的 Service 资源协同工作,自动实现 Pod 间通信。
-
1.2. 适用场景
- 云原生微服务架构
- 在需要严格流量控制和丰富可观测性的环境中表现尤为出色。
- 边缘计算
- 低延迟需求较高的场景,如 CDN 边缘节点和 IoT 环境。
- 高流量集群
- 适用于对吞吐量和性能要求极高的生产集群,例如电商、流媒体和金融服务。
- 多租户隔离
- 支持多租户网络环境中的强隔离需求。
1.3. Cilium的局限性
虽然 Cilium 在现代 Kubernetes 网络中表现出色,但它也存在一些缺点或需要注意的限制。
1. 高性能消耗
- 内存和 CPU 占用:由于需要在每个节点运行 Cilium Agent 和依赖 eBPF 加载程序,可能对节点资源消耗较高,尤其是在高流量场景下。
- 资源密集功能:如 Hubble(Cilium 的可观测性工具)可能进一步增加资源使用。
2. 依赖 Linux 内核版本
- eBPF 限制:Cilium 依赖 eBPF 技术,对 Linux 内核版本有要求,最低需要 4.19+,部分功能(如高级负载均衡)需要 5.x 或更高版本。
- 内核升级成本:在某些环境(如老旧系统或企业级环境)中,升级内核可能具有挑战性。
3. 学习曲线陡峭
- 复杂性:Cilium 引入了 eBPF 技术,与传统 CNI(如 Calico、Flannel)相比技术更复杂,需要深入理解 eBPF、Linux 内核网络栈和 Cilium 的配置方式。
4. 部署和管理复杂
- 高级功能配置繁琐:如替代
kube-proxy或配置高性能负载均衡,需要了解底层网络和 Kubernetes 的细节。 - 监控和故障排查难度:eBPF 程序运行在内核中,排查问题时无法直接查看传统用户态日志,需使用专用工具如
bpftool或 Hubble。
2. Cilium部署
2.1. 部署
部署文档可参考:
- https://docs.cilium.io/en/stable/installation/k8s-install-helm/
- https://docs.cilium.io/en/stable/gettingstarted/k8s-install-default/#k8s-install-quick
可以使用helm来部署
默认值:
- 默认的
clusterPoolIPv4PodCIDRList是10.0.0.0/8,需要保证pod CIDR跟node的CIDR不冲突。 - 默认
ipam.mode是cluster-pool,可不修改设置。
helm repo add cilium https://helm.cilium.io/
helm repo update
# 部署cilium
kubectl create ns cilium-system || true
helm install cilium cilium/cilium --namespace cilium-system \
--set ipam.mode=cluster-pool \
--set ipam.operator.clusterPoolIPv4PodCIDRList="10.244.0.0/16" \
--set ipam.operator.clusterPoolIPv4MaskSize=24
或者使用Cilium CLI 部署
# 安装cilium cli
curl -L --remote-name https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz
tar xzvf cilium-linux-amd64.tar.gz
sudo mv cilium /usr/local/bin
# 部署cilium套件
kubectl create ns cilium-system || true
cilium install --namespace=cilium-system \
--set ipam.mode=cluster-pool \
--set ipam.operator.clusterPoolIPv4PodCIDRList="10.244.0.0/16" \
--set ipam.operator.clusterPoolIPv4MaskSize=24
2.2. 部署检查
$ cilium status --wait --namespace cilium-system
/¯¯\
/¯¯\__/¯¯\ Cilium: OK
\__/¯¯\__/ Operator: OK
/¯¯\__/¯¯\ Hubble: disabled
\__/¯¯\__/ ClusterMesh: disabled
\__/
DaemonSet cilium Desired: 2, Ready: 2/2, Available: 2/2
Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2
Containers: cilium-operator Running: 2
cilium Running: 2
Image versions cilium quay.io/cilium/cilium:v1.9.5: 2
cilium-operator quay.io/cilium/operator-generic:v1.9.5: 2
网络连通性测试
$ cilium connectivity test
ℹ️ Monitor aggregation detected, will skip some flow validation steps
✨ [k8s-cluster] Creating namespace for connectivity check...
(...)
---------------------------------------------------------------------------------------------------------------------
📋 Test Report
---------------------------------------------------------------------------------------------------------------------
✅ 69/69 tests successful (0 warnings)
2.3. Cilium IPAM 模式
Cilium 支持以下两种常见的 IPAM 模式:
2.3.1. Cluster-Pool 模式(默认模式):
- 由 Cilium 自己管理 Pod IP 地址范围。
- 可以在部署时指定 CIDR 范围。
- 高灵活性:支持为不同节点或区域定义独立的 IP 地址池。
- 动态管理:可以动态调整 IP 地址池大小,适应集群的扩展需求。
- 无冲突设计:避免因节点增加或删除引起的 IP 地址冲突。
- 优化性能:减少对 Kubernetes 控制器的依赖,提高网络性能和资源利用率。
- 依赖 Cilium Operator:需要运行 Cilium Operator 来管理 IP 地址池,增加运维复杂性。
推荐场景
- 大型集群(> 500 节点)或超大规模集群。
- 需要跨区域或多节点池的复杂网络规划。
- 需要动态扩展 Pod IP 地址范围的集群。
- 集群运行 Cilium 并需要充分利用其高级功能(如 eBPF 加速、服务网格等)。
2.3.2. Kubernetes 模式:
- 使用 Kubernetes 自身的 IP 地址分配方式,例如由
kube-controller-manager通过--cluster-cidr进行管理。 - Cilium 从 Kubernetes 中获取分配给 Pod 的 IP 地址。
- 兼容性强:适配大多数 CNI 插件(包括 Cilium),无需额外配置。
- 灵活性有限:不支持细粒度的 IP 地址池管理,无法为特定节点或区域分配特定的 IP 范围。
推荐场景
- 小型或中型集群(< 500 节点)。
- 网络规划较为简单,无需复杂的 IP 地址管理。
- 需要快速部署并保持与 Kubernetes 默认行为一致。
3. Cilium架构及组件介绍
3.1. 架构图
参考官网: https://docs.cilium.io/en/stable/overview/component-overview/

3.2. 核心组件
1. Cilium Agent
- 运行在每个 Kubernetes 节点上,是 Cilium 的核心守护进程。
- 功能:
- 从 Kubernetes API Server 获取资源(如 Pod、Service 和网络策略)并转换为 eBPF 程序。
- 在每个节点上管理和加载 eBPF 程序到内核。
- 实现 L3/L4 和 L7 网络策略,并将策略下发到数据平面。
- 负责服务发现和负载均衡(取代 kube-proxy)。
2. Cilium CLI
- 命令行工具,用于安装、配置和调试 Cilium。
- 功能:
- 配置 Cilium 的网络策略。
- 查看 Cilium 和 Hubble 的运行状态。
- 调试 eBPF 程序。
3. CNI Plugin
- 当在节点上调度或终止pod时,Kubernetes会调用CNI插件(cilium-cni)。它与节点的Cilium API交互,触发必要的数据路径配置,为pod提供网络、负载平衡和网络策略。
4. Cilium Operator
- 在 Kubernetes 中运行的控制平面组件。
- 功能:
- 处理 IP 地址管理:管理 Pod 的 IP 池。
- 维护 Cilium Agent 与 Kubernetes 的集成。
- 处理节点间的拓扑变化(如节点加入或离开)。
3.3. 其他组件
1. eBPF 程序
- Cilium 的数据平面核心,运行在 Linux 内核中。
- 功能:
- 路由和转发:在节点间处理 Pod 的网络流量。
- 网络策略:实现 L3/L4 和 L7 的访问控制。
- 服务负载均衡:提供类似 kube-proxy 的服务转发功能,但性能更高。
- 监控和可观测性:收集网络流量数据,供 Hubble 或其他工具分析。
2. Hubble
- Cilium 的可观测性平台,用于实时监控和分析网络流量。
- 功能:
- 流量可视化:展示服务间的流量路径和统计。
- 流量追踪:捕获和调试网络问题。
- 延迟和错误率监控。
4. Cilium 的工作流
- 初始化:
- Cilium Agent 启动后,与 Kubernetes API Server 建立连接,监听资源变化。
- 策略配置:
- 用户定义的
NetworkPolicy或CiliumNetworkPolicy通过 Cilium Agent 传递到 eBPF 程序。 - eBPF 程序在内核中直接执行流量控制逻辑。
- 用户定义的
- 流量处理:
- 数据平面通过 eBPF 程序对网络流量进行路由、策略匹配和负载均衡。
- 监控和分析:
- eBPF 程序收集流量数据,发送到 Hubble。
- Hubble 将数据可视化,供用户监控和调试。
参考:
5.8 - k8s网关
5.8.1 - 安装APISIX
本文主要介绍通过k8s来部署apisix及apisix-ingress-controller,使用apisix作为k8s内Pod互相访问的网关。
1. 环境准备
1.1. 安装helm
参考:Helm | 安装
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm添加仓库
helm repo add apisix https://charts.apiseven.com
helm repo update
1.2. 安装ETCD
可以提前准备好etcd环境,也可以使用apisix官方的helm命令安装,但是需要存在默认是storageclass来提供pv挂载。
2. 一键安装全部
helm install apisix apisix/apisix --set gateway.type=NodePort --set ingress-controller.enabled=true --namespace=apisix --create-namespace
或通过以下方式分别安装各组件。
3. 安装apisix
参考:apisix-helm-chart/apisix.md at master · apache/apisix-helm-chart · GitHub
3.1. 安装
helm install apisix apisix/apisix --namespace apisix --create-namespace
卸载
helm uninstall apisix --namespace apisix
3.2. 修改配置
kubectl edit cm apisix -napisix
可选:
-
修改apisix端口。
-
修改etcd地址。
-
修改admin key值。
-
修改日志路径。
-
使用etcd存储stream配置或者静态文件存储stream配置
apisix:
node_listen: 8000 # APISIX listening port
config_center: etcd # etcd: use etcd to store the config value
# yaml: fetch the config value from local yaml file `/your_path/conf/apisix.yaml`
etcd:
host: "http://foo:2379" # etcd address
admin_key
-
name: "admin"
key: newsupersecurekey # 请修改 key 的值
role: admin
nginx_config:
error_log: /var/log/apisix_error.log
http:
access_log: /var/log/apisix_access.log
access_log_format: "$time_iso8601|$remote_addr - $remote_user|$http_host|\"$request\"|$status|$body_bytes_sent|$request_time|\"$http_referer\"|\"$http_user_agent\"|$upstream_addr|$upstream_status|$upstream_response_time|\"$upstream_scheme://$upstream_host$upstream_uri\""
plugin_attr:
log-rotate:
interval: 3600 # rotate interval (unit: second)
max_kept: 48 # max number of log files will be kept
enable_compression: false
4. 安装apisix-ingress-controller
参考:apisix-helm-chart/apisix-ingress-controller.md at master · apache/apisix-helm-chart · GitHub
4.1. 安装
helm install apisix-ingress-controller apisix/apisix-ingress-controller --namespace apisix --create-namespace
卸载
helm uninstall apisix-ingress-controller --namespace apisix
4.2. 修改配置
kubectl edit cm apisix-configmap -napisix
配置
-
apisix地址
-
apisix admin key
default_cluster_base_url: http://apisix-admin.apisix.svc.cluster.local:9180/apisix/admin
default_cluster_admin_key: "edd1c9f034335f136f87ad84b625c8f1"
5. 安装dashboard
5.1. 安装
helm repo add apisix https://charts.apiseven.com
helm repo update
helm install apisix-dashboard apisix/apisix-dashboard --namespace apisix --create-namespace
卸载
helm uninstall apisix-dashboard --namespace apisix
5.2. 修改配置
kubectl edit cm apisix-dashboard -napisix
-
端口
-
etcd地址
-
登录账号密码
data:
conf.yaml: |-
conf:
listen:
host: 0.0.0.0
port: 9000
etcd:
prefix: "/apisix"
endpoints:
- 10.65.240.210:2379
log:
error_log:
level: warn
file_path: /dev/stderr
access_log:
file_path: /dev/stdout
authentication:
secert: secert
expire_time: 3600
users:
- username: admin
password: admin
6. 查看helm安装列表
# helm list -n apisix
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
apisix apisix 1 2022-08-23 16:19:47.678174579 +0800 +08 deployed apisix-0.11.0 2.15.0
apisix-dashboard apisix 1 2022-08-23 20:36:37.55042356 +0800 +08 deployed apisix-dashboard-0.6.0 2.13.0
参考:
5.8.2 - 创建路由
本文主要介绍三种方式来创建apisix的路由规则。需要提前创建好k8s service作为路由的后端标识来关联endpoints。
0. 创建k8s service
apiVersion: v1
kind: Service
metadata:
name: {APP}-service
namespace: {NAMESPACE}
spec:
selector:
k8s-app: {APP}
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
路由规则主要包括:
-
hosts:域名
-
paths:访问路径
-
backends:
-
serviceName
-
servicePort
-
1. 使用ApisixRoute创建路由规则
使用ApisixRoute自定义CRD创建路由规则,具体参考:reference。
示例:
# httpbin-route.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: httpserver-route
spec:
http:
- name: rule1
match:
hosts:
- local.httpbin.org
paths:
- /*
backends:
- serviceName: httpbin
servicePort: 80
在k8s中创建ApisixRoute。
kubectl apply -f httpbin-route.yaml
2. 使用ingress创建路由规则
使用k8s ingress来创建路由规则,示例如下:
# httpbin-ingress.yaml
# Note use apiVersion is networking.k8s.io/v1, so please make sure your
# Kubernetes cluster version is v1.19.0 or higher.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httpserver-ingress
spec:
# apisix-ingress-controller is only interested in Ingress
# resources with the matched ingressClass name, in our case,
# it's apisix.
ingressClassName: apisix
rules:
- host: local.httpbin.org
http:
paths:
- backend:
service:
name: httpbin
port:
number: 80
path: /
pathType: Prefix
创建k8s ingress。
kubectl apply -f httpbin-ingress.yaml
3. 使用Admin API创建路由规则
直接调用Admin API或者使用dashboard创建路由规则。
3.1. 一键创建路由和upstream
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
"methods": ["GET"],
"host": "example.com",
"uri": "/anything/*",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
3.2. 分开创建路由和upstream
推荐使用分开创建路由和upstream。
创建upstream
curl "http://127.0.0.1:9180/apisix/admin/upstreams/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}'
创建路由绑定upstream
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
"uris": ["/get","/list"],
"host": "httpbin.org",
"upstream_id": "1"
}'
删除upstream
DELETE /apisix/admin/upstreams/{id}
删除route
DELETE /apisix/admin/routes/{id}
4. 验证路由规则
基于上述的方式,apisix-ingress-controller会调用apisix admin的接口自动创建routes和upstreams两个信息存入etcd,通过业务域名访问apisix就可以访问到具体的pod。
服务调用
将业务域名解析到apisix的IP上(如果是物理机部署可以是VIP,或者k8s部署的clusterIP)。
访问业务域名:
curl -v http://local.httpbin.org
5. 查看etcd中的路由规则
etcdctl get /apisix --prefix
5.1. routes
/apisix/routes/
通过ingress创建的routes
{
"host": "local.httpbin.org",
"create_time": 1661251916,
"name": "ing_default_httpserver-ingress_37a4f3ae",
"status": 1,
"uris": [
"\/",
"\/*"
],
"upstream_id": "5ce57b8e",
"labels": {
"managed-by": "apisix-ingress-controller"
},
"priority": 0,
"desc": "Created by apisix-ingress-controller, DO NOT modify it manually",
"update_time": 1661397119,
"id": "148730bb"
}
通过ApisixRoute创建的routes
{
"priority": 0,
"create_time": 1661397584,
"name": "default_httpserver-route_rule1",
"status": 1,
"uris": [
"\/*"
],
"upstream_id": "5ce57b8e",
"hosts": [
"local.httpbin.org"
],
"labels": {
"managed-by": "apisix-ingress-controller"
},
"desc": "Created by apisix-ingress-controller, DO NOT modify it manually",
"update_time": 1661397584,
"id": "add8e28c"
}
5.2. upstreams
/apisix/upstreams/5ce57b8e
相同的backend,使用ingress或ApisixRoute创建后生成的upstreams相同。
{
"scheme": "http",
"pass_host": "pass",
"name": "default_httpbin_80",
"nodes": [
{
"host": "10.244.3.167",
"priority": 0,
"port": 80,
"weight": 100
}
],
"type": "roundrobin",
"labels": {
"managed-by": "apisix-ingress-controller"
},
"hash_on": "vars",
"create_time": 1661251916,
"id": "5ce57b8e",
"update_time": 1661397584,
"desc": "Created by apisix-ingress-controller, DO NOT modify it manually"
}
参考:
5.8.3 - ingress-controller原理
ingress-controller架构图

ingress-controller流程图

ApisixRoute同步逻辑

数据结构转换

参考:
5.8.4 - APISIX配置
本文主要记录apisix相关组件默认配置,便于查阅。
1. apisix配置
参考:apisix/config-default.yaml at master · apache/apisix · GitHub
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# PLEASE DO NOT UPDATE THIS FILE!
# If you want to set the specified configuration value, you can set the new
# value in the conf/config.yaml file.
#
apisix:
# node_listen: 9080 # APISIX listening port
node_listen: # This style support multiple ports
- 9080
# - port: 9081
# enable_http2: true # If not set, the default value is `false`.
# - ip: 127.0.0.2 # Specific IP, If not set, the default value is `0.0.0.0`.
# port: 9082
# enable_http2: true
enable_admin: true
enable_admin_cors: true # Admin API support CORS response headers.
enable_dev_mode: false # Sets nginx worker_processes to 1 if set to true
enable_reuseport: true # Enable nginx SO_REUSEPORT switch if set to true.
show_upstream_status_in_response_header: false # when true all upstream status write to `X-APISIX-Upstream-Status` otherwise only 5xx code
enable_ipv6: true
config_center: etcd # etcd: use etcd to store the config value
# yaml: fetch the config value from local yaml file `/your_path/conf/apisix.yaml`
#proxy_protocol: # Proxy Protocol configuration
#listen_http_port: 9181 # The port with proxy protocol for http, it differs from node_listen and admin_listen.
# This port can only receive http request with proxy protocol, but node_listen & admin_listen
# can only receive http request. If you enable proxy protocol, you must use this port to
# receive http request with proxy protocol
#listen_https_port: 9182 # The port with proxy protocol for https
#enable_tcp_pp: true # Enable the proxy protocol for tcp proxy, it works for stream_proxy.tcp option
#enable_tcp_pp_to_upstream: true # Enables the proxy protocol to the upstream server
enable_server_tokens: true # Whether the APISIX version number should be shown in Server header.
# It's enabled by default.
# configurations to load third party code and/or override the builtin one.
extra_lua_path: "" # extend lua_package_path to load third party code
extra_lua_cpath: "" # extend lua_package_cpath to load third party code
#lua_module_hook: "my_project.my_hook" # the hook module which will be used to inject third party code into APISIX
proxy_cache: # Proxy Caching configuration
cache_ttl: 10s # The default caching time in disk if the upstream does not specify the cache time
zones: # The parameters of a cache
- name: disk_cache_one # The name of the cache, administrator can specify
# which cache to use by name in the admin api (disk|memory)
memory_size: 50m # The size of shared memory, it's used to store the cache index for
# disk strategy, store cache content for memory strategy (disk|memory)
disk_size: 1G # The size of disk, it's used to store the cache data (disk)
disk_path: /tmp/disk_cache_one # The path to store the cache data (disk)
cache_levels: 1:2 # The hierarchy levels of a cache (disk)
#- name: disk_cache_two
# memory_size: 50m
# disk_size: 1G
# disk_path: "/tmp/disk_cache_two"
# cache_levels: "1:2"
- name: memory_cache
memory_size: 50m
allow_admin: # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow
- 127.0.0.0/24 # If we don't set any IP list, then any IP access is allowed by default.
#- "::/64"
#admin_listen: # use a separate port
# ip: 127.0.0.1 # Specific IP, if not set, the default value is `0.0.0.0`.
# port: 9180
#https_admin: true # enable HTTPS when use a separate port for Admin API.
# Admin API will use conf/apisix_admin_api.crt and conf/apisix_admin_api.key as certificate.
admin_api_mtls: # Depends on `admin_listen` and `https_admin`.
admin_ssl_cert: "" # Path of your self-signed server side cert.
admin_ssl_cert_key: "" # Path of your self-signed server side key.
admin_ssl_ca_cert: "" # Path of your self-signed ca cert.The CA is used to sign all admin api callers' certificates.
admin_api_version: v3 # The version of admin api, latest version is v3.
# Default token when use API to call for Admin API.
# *NOTE*: Highly recommended to modify this value to protect APISIX's Admin API.
# Disabling this configuration item means that the Admin API does not
# require any authentication.
admin_key:
-
name: admin
key: edd1c9f034335f136f87ad84b625c8f1
role: admin # admin: manage all configuration data
# viewer: only can view configuration data
-
name: viewer
key: 4054f7cf07e344346cd3f287985e76a2
role: viewer
delete_uri_tail_slash: false # delete the '/' at the end of the URI
# The URI normalization in servlet is a little different from the RFC's.
# See https://github.com/jakartaee/servlet/blob/master/spec/src/main/asciidoc/servlet-spec-body.adoc#352-uri-path-canonicalization,
# which is used under Tomcat.
# Turn this option on if you want to be compatible with servlet when matching URI path.
normalize_uri_like_servlet: false
router:
http: radixtree_uri # radixtree_uri: match route by uri(base on radixtree)
# radixtree_host_uri: match route by host + uri(base on radixtree)
# radixtree_uri_with_parameter: like radixtree_uri but match uri with parameters,
# see https://github.com/api7/lua-resty-radixtree/#parameters-in-path for
# more details.
ssl: radixtree_sni # radixtree_sni: match route by SNI(base on radixtree)
#stream_proxy: # TCP/UDP proxy
# only: true # use stream proxy only, don't enable HTTP stuff
# tcp: # TCP proxy port list
# - addr: 9100
# tls: true
# - addr: "127.0.0.1:9101"
# udp: # UDP proxy port list
# - 9200
# - "127.0.0.1:9201"
#dns_resolver: # If not set, read from `/etc/resolv.conf`
# - 1.1.1.1
# - 8.8.8.8
#dns_resolver_valid: 30 # if given, override the TTL of the valid records. The unit is second.
resolver_timeout: 5 # resolver timeout
enable_resolv_search_opt: true # enable search option in resolv.conf
ssl:
enable: true
listen: # APISIX listening port in https.
- port: 9443
enable_http2: true
# - ip: 127.0.0.3 # Specific IP, If not set, the default value is `0.0.0.0`.
# port: 9445
# enable_http2: true
#ssl_trusted_certificate: /path/to/ca-cert # Specifies a file path with trusted CA certificates in the PEM format
# used to verify the certificate when APISIX needs to do SSL/TLS handshaking
# with external services (e.g. etcd)
ssl_protocols: TLSv1.2 TLSv1.3
ssl_ciphers: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl_session_tickets: false # disable ssl_session_tickets by default for 'ssl_session_tickets' would make Perfect Forward Secrecy useless.
# ref: https://github.com/mozilla/server-side-tls/issues/135
key_encrypt_salt: edd1c9f0985e76a2 # If not set, will save origin ssl key into etcd.
# If set this, must be a string of length 16. And it will encrypt ssl key with AES-128-CBC
# !!! So do not change it after saving your ssl, it can't decrypt the ssl keys have be saved if you change !!
#fallback_sni: "my.default.domain" # If set this, when the client doesn't send SNI during handshake, the fallback SNI will be used instead
enable_control: true
#control:
# ip: 127.0.0.1
# port: 9090
disable_sync_configuration_during_start: false # safe exit. Remove this once the feature is stable
nginx_config: # config for render the template to generate nginx.conf
#user: root # specifies the execution user of the worker process.
# the "user" directive makes sense only if the master process runs with super-user privileges.
# if you're not root user,the default is current user.
error_log: logs/error.log
error_log_level: warn # warn,error
worker_processes: auto # if you want use multiple cores in container, you can inject the number of cpu as environment variable "APISIX_WORKER_PROCESSES"
enable_cpu_affinity: true # enable cpu affinity, this is just work well only on physical machine
worker_rlimit_nofile: 20480 # the number of files a worker process can open, should be larger than worker_connections
worker_shutdown_timeout: 240s # timeout for a graceful shutdown of worker processes
max_pending_timers: 16384 # increase it if you see "too many pending timers" error
max_running_timers: 4096 # increase it if you see "lua_max_running_timers are not enough" error
event:
worker_connections: 10620
#envs: # allow to get a list of environment variables
# - TEST_ENV
meta:
lua_shared_dict:
prometheus-metrics: 15m
stream:
enable_access_log: false # enable access log or not, default false
access_log: logs/access_stream.log
access_log_format: "$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time"
# create your custom log format by visiting http://nginx.org/en/docs/varindex.html
access_log_format_escape: default # allows setting json or default characters escaping in variables
lua_shared_dict:
etcd-cluster-health-check-stream: 10m
lrucache-lock-stream: 10m
plugin-limit-conn-stream: 10m
# As user can add arbitrary configurations in the snippet,
# it is user's responsibility to check the configurations
# don't conflict with APISIX.
main_configuration_snippet: |
# Add custom Nginx main configuration to nginx.conf.
# The configuration should be well indented!
http_configuration_snippet: |
# Add custom Nginx http configuration to nginx.conf.
# The configuration should be well indented!
http_server_configuration_snippet: |
# Add custom Nginx http server configuration to nginx.conf.
# The configuration should be well indented!
http_server_location_configuration_snippet: |
# Add custom Nginx http server location configuration to nginx.conf.
# The configuration should be well indented!
http_admin_configuration_snippet: |
# Add custom Nginx admin server configuration to nginx.conf.
# The configuration should be well indented!
http_end_configuration_snippet: |
# Add custom Nginx http end configuration to nginx.conf.
# The configuration should be well indented!
stream_configuration_snippet: |
# Add custom Nginx stream configuration to nginx.conf.
# The configuration should be well indented!
http:
enable_access_log: true # enable access log or not, default true
access_log: logs/access.log
access_log_format: "$remote_addr - $remote_user [$time_local] $http_host \"$request\" $status $body_bytes_sent $request_time \"$http_referer\" \"$http_user_agent\" $upstream_addr $upstream_status $upstream_response_time \"$upstream_scheme://$upstream_host$upstream_uri\""
access_log_format_escape: default # allows setting json or default characters escaping in variables
keepalive_timeout: 60s # timeout during which a keep-alive client connection will stay open on the server side.
client_header_timeout: 60s # timeout for reading client request header, then 408 (Request Time-out) error is returned to the client
client_body_timeout: 60s # timeout for reading client request body, then 408 (Request Time-out) error is returned to the client
client_max_body_size: 0 # The maximum allowed size of the client request body.
# If exceeded, the 413 (Request Entity Too Large) error is returned to the client.
# Note that unlike Nginx, we don't limit the body size by default.
send_timeout: 10s # timeout for transmitting a response to the client.then the connection is closed
underscores_in_headers: "on" # default enables the use of underscores in client request header fields
real_ip_header: X-Real-IP # http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_header
real_ip_recursive: "off" # http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_recursive
real_ip_from: # http://nginx.org/en/docs/http/ngx_http_realip_module.html#set_real_ip_from
- 127.0.0.1
- "unix:"
#custom_lua_shared_dict: # add custom shared cache to nginx.conf
# ipc_shared_dict: 100m # custom shared cache, format: `cache-key: cache-size`
# Enables or disables passing of the server name through TLS Server Name Indication extension (SNI, RFC 6066)
# when establishing a connection with the proxied HTTPS server.
proxy_ssl_server_name: true
upstream:
keepalive: 320 # Sets the maximum number of idle keepalive connections to upstream servers that are preserved in the cache of each worker process.
# When this number is exceeded, the least recently used connections are closed.
keepalive_requests: 1000 # Sets the maximum number of requests that can be served through one keepalive connection.
# After the maximum number of requests is made, the connection is closed.
keepalive_timeout: 60s # Sets a timeout during which an idle keepalive connection to an upstream server will stay open.
charset: utf-8 # Adds the specified charset to the "Content-Type" response header field, see
# http://nginx.org/en/docs/http/ngx_http_charset_module.html#charset
variables_hash_max_size: 2048 # Sets the maximum size of the variables hash table.
lua_shared_dict:
internal-status: 10m
plugin-limit-req: 10m
plugin-limit-count: 10m
prometheus-metrics: 10m
plugin-limit-conn: 10m
upstream-healthcheck: 10m
worker-events: 10m
lrucache-lock: 10m
balancer-ewma: 10m
balancer-ewma-locks: 10m
balancer-ewma-last-touched-at: 10m
plugin-limit-count-redis-cluster-slot-lock: 1m
tracing_buffer: 10m
plugin-api-breaker: 10m
etcd-cluster-health-check: 10m
discovery: 1m
jwks: 1m
introspection: 10m
access-tokens: 1m
ext-plugin: 1m
kubernetes: 1m
tars: 1m
etcd:
host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
- "http://127.0.0.1:2379" # multiple etcd address, if your etcd cluster enables TLS, please use https scheme,
# e.g. https://127.0.0.1:2379.
prefix: /apisix # apisix configurations prefix
#timeout: 30 # 30 seconds
#resync_delay: 5 # when sync failed and a rest is needed, resync after the configured seconds plus 50% random jitter
#health_check_timeout: 10 # etcd retry the unhealthy nodes after the configured seconds
startup_retry: 2 # the number of retry to etcd during the startup, default to 2
#user: root # root username for etcd
#password: 5tHkHhYkjr6cQY # root password for etcd
tls:
# To enable etcd client certificate you need to build APISIX-Base, see
# https://apisix.apache.org/docs/apisix/FAQ#how-do-i-build-the-apisix-base-environment
#cert: /path/to/cert # path of certificate used by the etcd client
#key: /path/to/key # path of key used by the etcd client
verify: true # whether to verify the etcd endpoint certificate when setup a TLS connection to etcd,
# the default value is true, e.g. the certificate will be verified strictly.
#sni: # the SNI for etcd TLS requests. If missed, the host part of the URL will be used.
# HashiCorp Vault storage backend for sensitive data retrieval. The config shows an example of what APISIX expects if you
# wish to integrate Vault for secret (sensetive string, public private keys etc.) retrieval. APISIX communicates with Vault
# server HTTP APIs. By default, APISIX doesn't need this configuration.
# vault:
# host: "http://0.0.0.0:8200" # The host address where the vault server is running.
# timeout: 10 # request timeout 30 seconds
# token: root # Authentication token to access Vault HTTP APIs
# prefix: kv/apisix # APISIX supports vault kv engine v1, where sensitive data are being stored
# and retrieved through vault HTTP APIs. enabling a prefix allows you to better enforcement of
# policies, generate limited scoped tokens and tightly control the data that can be accessed
# from APISIX.
#discovery: # service discovery center
# dns:
# servers:
# - "127.0.0.1:8600" # use the real address of your dns server
# eureka:
# host: # it's possible to define multiple eureka hosts addresses of the same eureka cluster.
# - "http://127.0.0.1:8761"
# prefix: /eureka/
# fetch_interval: 30 # default 30s
# weight: 100 # default weight for node
# timeout:
# connect: 2000 # default 2000ms
# send: 2000 # default 2000ms
# read: 5000 # default 5000ms
# nacos:
# host:
# - "http://${username}:${password}@${host1}:${port1}"
# prefix: "/nacos/v1/"
# fetch_interval: 30 # default 30 sec
# weight: 100 # default 100
# timeout:
# connect: 2000 # default 2000 ms
# send: 2000 # default 2000 ms
# read: 5000 # default 5000 ms
# consul_kv:
# servers:
# - "http://127.0.0.1:8500"
# - "http://127.0.0.1:8600"
# prefix: "upstreams"
# skip_keys: # if you need to skip special keys
# - "upstreams/unused_api/"
# timeout:
# connect: 2000 # default 2000 ms
# read: 2000 # default 2000 ms
# wait: 60 # default 60 sec
# weight: 1 # default 1
# fetch_interval: 3 # default 3 sec, only take effect for keepalive: false way
# keepalive: true # default true, use the long pull way to query consul servers
# default_server: # you can define default server when missing hit
# host: "127.0.0.1"
# port: 20999
# metadata:
# fail_timeout: 1 # default 1 ms
# weight: 1 # default 1
# max_fails: 1 # default 1
# dump: # if you need, when registered nodes updated can dump into file
# path: "logs/consul_kv.dump"
# expire: 2592000 # unit sec, here is 30 day
# kubernetes:
# service:
# schema: https #apiserver schema, options [http, https], default https
# host: ${KUBERNETES_SERVICE_HOST} #apiserver host, options [ipv4, ipv6, domain, environment variable], default ${KUBERNETES_SERVICE_HOST}
# port: ${KUBERNETES_SERVICE_PORT} #apiserver port, options [port number, environment variable], default ${KUBERNETES_SERVICE_PORT}
# client:
# # serviceaccount token or path of serviceaccount token_file
# token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
# # token: |-
# # eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEif
# # 6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEifeyJhbGciOiJSUzI1NiIsImtpZCI
# # kubernetes discovery plugin support use namespace_selector
# # you can use one of [equal, not_equal, match, not_match] filter namespace
# namespace_selector:
# # only save endpoints with namespace equal default
# equal: default
# # only save endpoints with namespace not equal default
# #not_equal: default
# # only save endpoints with namespace match one of [default, ^my-[a-z]+$]
# #match:
# #- default
# #- ^my-[a-z]+$
# # only save endpoints with namespace not match one of [default, ^my-[a-z]+$ ]
# #not_match:
# #- default
# #- ^my-[a-z]+$
# # kubernetes discovery plugin support use label_selector
# # for the expression of label_selector, please refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
# label_selector: |-
# first="a",second="b"
graphql:
max_size: 1048576 # the maximum size limitation of graphql in bytes, default 1MiB
#ext-plugin:
#cmd: ["ls", "-l"]
plugins: # plugin list (sorted by priority)
- real-ip # priority: 23000
- client-control # priority: 22000
- proxy-control # priority: 21990
- request-id # priority: 12015
- zipkin # priority: 12011
#- skywalking # priority: 12010
#- opentelemetry # priority: 12009
- ext-plugin-pre-req # priority: 12000
- fault-injection # priority: 11000
- mocking # priority: 10900
- serverless-pre-function # priority: 10000
#- batch-requests # priority: 4010
- cors # priority: 4000
- ip-restriction # priority: 3000
- ua-restriction # priority: 2999
- referer-restriction # priority: 2990
- csrf # priority: 2980
- uri-blocker # priority: 2900
- request-validation # priority: 2800
- openid-connect # priority: 2599
- authz-casbin # priority: 2560
- authz-casdoor # priority: 2559
- wolf-rbac # priority: 2555
- ldap-auth # priority: 2540
- hmac-auth # priority: 2530
- basic-auth # priority: 2520
- jwt-auth # priority: 2510
- key-auth # priority: 2500
- consumer-restriction # priority: 2400
- forward-auth # priority: 2002
- opa # priority: 2001
- authz-keycloak # priority: 2000
#- error-log-logger # priority: 1091
- proxy-mirror # priority: 1010
- proxy-cache # priority: 1009
- proxy-rewrite # priority: 1008
- workflow # priority: 1006
- api-breaker # priority: 1005
- limit-conn # priority: 1003
- limit-count # priority: 1002
- limit-req # priority: 1001
#- node-status # priority: 1000
- gzip # priority: 995
- server-info # priority: 990
- traffic-split # priority: 966
- redirect # priority: 900
- response-rewrite # priority: 899
- kafka-proxy # priority: 508
#- dubbo-proxy # priority: 507
- grpc-transcode # priority: 506
- grpc-web # priority: 505
- public-api # priority: 501
- prometheus # priority: 500
- datadog # priority: 495
- echo # priority: 412
- loggly # priority: 411
- http-logger # priority: 410
- splunk-hec-logging # priority: 409
- skywalking-logger # priority: 408
- google-cloud-logging # priority: 407
- sls-logger # priority: 406
- tcp-logger # priority: 405
- kafka-logger # priority: 403
- rocketmq-logger # priority: 402
- syslog # priority: 401
- udp-logger # priority: 400
- file-logger # priority: 399
- clickhouse-logger # priority: 398
- tencent-cloud-cls # priority: 397
#- log-rotate # priority: 100
# <- recommend to use priority (0, 100) for your custom plugins
- example-plugin # priority: 0
- aws-lambda # priority: -1899
- azure-functions # priority: -1900
- openwhisk # priority: -1901
- serverless-post-function # priority: -2000
- ext-plugin-post-req # priority: -3000
- ext-plugin-post-resp # priority: -4000
stream_plugins: # sorted by priority
- ip-restriction # priority: 3000
- limit-conn # priority: 1003
- mqtt-proxy # priority: 1000
#- prometheus # priority: 500
- syslog # priority: 401
# <- recommend to use priority (0, 100) for your custom plugins
#wasm:
#plugins:
#- name: wasm_log
#priority: 7999
#file: t/wasm/log/main.go.wasm
#xrpc:
#protocols:
#- name: pingpong
plugin_attr:
log-rotate:
interval: 3600 # rotate interval (unit: second)
max_kept: 168 # max number of log files will be kept
max_size: -1 # max size bytes of log files to be rotated, size check would be skipped with a value less than 0
enable_compression: false # enable log file compression(gzip) or not, default false
skywalking:
service_name: APISIX
service_instance_name: APISIX Instance Name
endpoint_addr: http://127.0.0.1:12800
opentelemetry:
trace_id_source: x-request-id
resource:
service.name: APISIX
collector:
address: 127.0.0.1:4318
request_timeout: 3
request_headers:
Authorization: token
batch_span_processor:
drop_on_queue_full: false
max_queue_size: 1024
batch_timeout: 2
inactive_timeout: 1
max_export_batch_size: 16
prometheus:
export_uri: /apisix/prometheus/metrics
metric_prefix: apisix_
enable_export_server: true
export_addr:
ip: 127.0.0.1
port: 9091
#metrics:
# http_status:
# # extra labels from nginx variables
# extra_labels:
# # the label name doesn't need to be the same as variable name
# # below labels are only examples, you could add any valid variables as you need
# - upstream_addr: $upstream_addr
# - upstream_status: $upstream_status
# http_latency:
# extra_labels:
# - upstream_addr: $upstream_addr
# bandwidth:
# extra_labels:
# - upstream_addr: $upstream_addr
server-info:
report_ttl: 60 # live time for server info in etcd (unit: second)
dubbo-proxy:
upstream_multiplex_count: 32
request-id:
snowflake:
enable: false
snowflake_epoc: 1609459200000 # the starting timestamp is expressed in milliseconds
data_machine_bits: 12 # data machine bit, maximum 31, because Lua cannot do bit operations greater than 31
sequence_bits: 10 # each machine generates a maximum of (1 << sequence_bits) serial numbers per millisecond
data_machine_ttl: 30 # live time for data_machine in etcd (unit: second)
data_machine_interval: 10 # lease renewal interval in etcd (unit: second)
proxy-mirror:
timeout: # proxy timeout in mirrored sub-request
connect: 60s
read: 60s
send: 60s
# redirect:
# https_port: 8443 # the default port for use by HTTP redirects to HTTPS
#deployment:
# role: traditional
# role_traditional:
# config_provider: etcd
# etcd:
# host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
# - "http://127.0.0.1:2379" # multiple etcd address, if your etcd cluster enables TLS, please use https scheme,
# # e.g. https://127.0.0.1:2379.
# prefix: /apisix # configuration prefix in etcd
# timeout: 30 # 30 seconds
2. apisix-ingress-controller
参考:apisix-ingress-controller/config-default.yaml at master · apache/apisix-ingress-controller · GitHub
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# log options
log_level: "info" # the error log level, default is info, optional values are:
# debug
# info
# warn
# error
# panic
# fatal
log_output: "stderr" # the output file path of error log, default is stderr, when
# the file path is "stderr" or "stdout", logs are marshalled
# plainly, which is more readable for human; otherwise logs
# are marshalled in JSON format, which can be parsed by
# programs easily.
log_rotate_output_path: "" # rotate output path, the logs will be written in this file
log_rotation_max_size: 100 # rotate max size, max size in megabytes of log file before it get rotated. It defaults to 100
log_rotation_max_age: 0 # rotate max age, max age of old log files to retain
log_rotation_max_backups: 0 # rotate max backups, max numbers of old log files to retain
cert_file: "/etc/webhook/certs/cert.pem" # the TLS certificate file path.
key_file: "/etc/webhook/certs/key.pem" # the TLS key file path.
http_listen: ":8080" # the HTTP Server listen address, default is ":8080"
https_listen: ":8443" # the HTTPS Server listen address, default is ":8443"
ingress_publish_service: "" # the controller will use the Endpoint of this Service to
# update the status information of the Ingress resource.
# The format is "namespace/svc-name" to solve the situation that
# the data plane and the controller are not deployed in the same namespace.
ingress_status_address: [] # when there is no available information on the Service
# used for publishing on the data plane,
# the static address provided here will be
# used to update the status information of Ingress.
# When ingress-publish-service is specified at the same time, ingress-status-address is preferred.
# For example, no available LB exists in the bare metal environment.
enable_profiling: true # enable profiling via web interfaces
# host:port/debug/pprof, default is true.
apisix-resource-sync-interval: "300s" # Default interval for synchronizing Kubernetes resources to APISIX
# Kubernetes related configurations.
kubernetes:
kubeconfig: "" # the Kubernetes configuration file path, default is
# "", so the in-cluster configuration will be used.
resync_interval: "6h" # how long should apisix-ingress-controller
# re-synchronizes with Kubernetes, default is 6h,
# and the minimal resync interval is 30s.
app_namespaces: ["*"] # namespace list that controller will watch for resources,
# by default all namespaces (represented by "*") are watched.
# The `app_namespace` is deprecated, using `namespace_selector` instead since version 1.4.0
namespace_selector: [""] # namespace_selector represent basis for selecting managed namespaces.
# the field is support since version 1.4.0
# For example, "apisix.ingress=watching", so ingress will watching the namespaces which labels "apisix.ingress=watching"
election_id: "ingress-apisix-leader" # the election id for the controller leader campaign,
# only the leader will watch and delivery resource changes,
# other instances (as candidates) stand by.
ingress_class: "apisix" # the class of an Ingress object is set using the field
# IngressClassName in Kubernetes clusters version v1.18.0
# or higher or the annotation "kubernetes.io/ingress.class"
# (deprecated).
ingress_version: "networking/v1" # the supported ingress api group version, can be "networking/v1beta1"
# , "networking/v1" (for Kubernetes version v1.19.0 or higher), and
# "extensions/v1beta1", default is "networking/v1".
watch_endpointslices: false # whether to watch EndpointSlices rather than Endpoints.
apisix_route_version: "apisix.apache.org/v2" # the supported apisixroute api group version.
# the latest version is "apisix.apache.org/v2".
enable_gateway_api: false # whether to enable support for Gateway API.
# Note: This feature is currently under development and may not work as expected.
# It is not recommended to use it in a production environment.
# Before we announce support for it to reach Beta level or GA.
api_version: apisix.apache.org/v2 # the default value of API version is "apisix.apache.org/v2", support "apisix.apache.org/v2beta3" and "apisix.apache.org/v2".
# APISIX related configurations.
apisix:
default_cluster_base_url: "http://127.0.0.1:9080/apisix/admin" # The base url of admin api / manager api
# of the default APISIX cluster
default_cluster_admin_key: "" # the admin key used for the authentication of admin api / manager api in the
# default APISIX cluster, by default this field is unset.
default_cluster_name: "default" # name of the default APISIX cluster.
3. apisix-dashboard
参考:apisix-dashboard/conf.yaml at master · apache/apisix-dashboard · GitHub
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# yamllint disable rule:comments-indentation
conf:
listen:
# host: 127.0.0.1 # the address on which the `Manager API` should listen.
# The default value is 0.0.0.0, if want to specify, please enable it.
# This value accepts IPv4, IPv6, and hostname.
port: 9000 # The port on which the `Manager API` should listen.
# ssl:
# host: 127.0.0.1 # the address on which the `Manager API` should listen for HTTPS.
# The default value is 0.0.0.0, if want to specify, please enable it.
# port: 9001 # The port on which the `Manager API` should listen for HTTPS.
# cert: "/tmp/cert/example.crt" # Path of your SSL cert.
# key: "/tmp/cert/example.key" # Path of your SSL key.
allow_list: # If we don't set any IP list, then any IP access is allowed by default.
- 127.0.0.1 # The rules are checked in sequence until the first match is found.
- ::1 # In this example, access is allowed only for IPv4 network 127.0.0.1, and for IPv6 network ::1.
# It also support CIDR like 192.168.1.0/24 and 2001:0db8::/32
etcd:
endpoints: # supports defining multiple etcd host addresses for an etcd cluster
- 127.0.0.1:2379
# yamllint disable rule:comments-indentation
# etcd basic auth info
# username: "root" # ignore etcd username if not enable etcd auth
# password: "123456" # ignore etcd password if not enable etcd auth
mtls:
key_file: "" # Path of your self-signed client side key
cert_file: "" # Path of your self-signed client side cert
ca_file: "" # Path of your self-signed ca cert, the CA is used to sign callers' certificates
# prefix: /apisix # apisix config's prefix in etcd, /apisix by default
log:
error_log:
level: warn # supports levels, lower to higher: debug, info, warn, error, panic, fatal
file_path:
logs/error.log # supports relative path, absolute path, standard output
# such as: logs/error.log, /tmp/logs/error.log, /dev/stdout, /dev/stderr
# such as absolute path on Windows: winfile:///C:\error.log
access_log:
file_path:
logs/access.log # supports relative path, absolute path, standard output
# such as: logs/access.log, /tmp/logs/access.log, /dev/stdout, /dev/stderr
# such as absolute path on Windows: winfile:///C:\access.log
# log example: 2020-12-09T16:38:09.039+0800 INFO filter/logging.go:46 /apisix/admin/routes/r1 {"status": 401, "host": "127.0.0.1:9000", "query": "asdfsafd=adf&a=a", "requestId": "3d50ecb8-758c-46d1-af5b-cd9d1c820156", "latency": 0, "remoteIP": "127.0.0.1", "method": "PUT", "errs": []}
max_cpu: 0 # supports tweaking with the number of OS threads are going to be used for parallelism. Default value: 0 [will use max number of available cpu cores considering hyperthreading (if any)]. If the value is negative, is will not touch the existing parallelism profile.
# security:
# access_control_allow_origin: "http://httpbin.org"
# access_control_allow_credentials: true # support using custom cors configration
# access_control_allow_headers: "Authorization"
# access_control-allow_methods: "*"
# x_frame_options: "deny"
# content_security_policy: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; frame-src xx.xx.xx.xx:3000" # You can set frame-src to provide content for your grafana panel.
authentication:
secret:
secret # secret for jwt token generation.
# NOTE: Highly recommended to modify this value to protect `manager api`.
# if it's default value, when `manager api` start, it will generate a random string to replace it.
expire_time: 3600 # jwt token expire time, in second
users: # yamllint enable rule:comments-indentation
- username: admin # username and password for login `manager api`
password: admin
- username: user
password: user
plugins: # plugin list (sorted in alphabetical order)
- api-breaker
- authz-keycloak
- basic-auth
- batch-requests
- consumer-restriction
- cors
# - dubbo-proxy
- echo
# - error-log-logger
# - example-plugin
- fault-injection
- grpc-transcode
- hmac-auth
- http-logger
- ip-restriction
- jwt-auth
- kafka-logger
- key-auth
- limit-conn
- limit-count
- limit-req
# - log-rotate
# - node-status
- openid-connect
- prometheus
- proxy-cache
- proxy-mirror
- proxy-rewrite
- redirect
- referer-restriction
- request-id
- request-validation
- response-rewrite
- serverless-post-function
- serverless-pre-function
# - skywalking
- sls-logger
- syslog
- tcp-logger
- udp-logger
- uri-blocker
- wolf-rbac
- zipkin
- server-info
- traffic-split
6 - 容器存储
6.1 - 存储卷概念
6.1.1 - Volume介绍
1. volume概述
- 容器上的文件生命周期同容器的生命周期一致,即容器挂掉之后,容器将会以最初镜像中的文件系统内容启动,之前容器运行时产生的文件将会丢失。
- Pod的volume的生命周期同Pod的生命周期一致,当Pod被删除的时候,对应的volume才会被删除。即Pod中的容器重启时,之前的文件仍可以保存。
容器中的进程看到的是由其 Docker 镜像和卷组成的文件系统视图。
Pod volume的使用方式
Pod 中的每个容器都必须独立指定每个卷的挂载位置,需要给Pod配置volume相关参数。
Pod的volume关键字段如下:
- spec.volumes:提供怎样的数据卷
- spec.containers.volumeMounts:挂载到容器的什么路径
2. volume类型
2.1. emptyDir
1、特点
- 会创建
emptyDir对应的目录,默认为空(如果该目录原来有文件也会被重置为空) - Pod中的不同容器可以在目录中读写相同文件(即Pod中的不同容器可以通过该方式来共享文件)
- 当Pod被删除,
emptyDir中的数据将被永久删除,如果只是Pod挂掉该数据还会保留
2、使用场景
-
不同容器之间共享文件(例如日志采集等)
-
暂存空间,例如用于基于磁盘的合并排序
-
用作长时间计算崩溃恢复时的检查点
3、示例
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
2.2. hostPath
1、特点
- 会将宿主机的目录或文件挂载到Pod中
2、使用场景
-
运行需要访问 Docker 内部的容器;使用
/var/lib/docker的hostPath -
在容器中运行 cAdvisor;使用
/dev/cgroups的hostPath -
其他使用到宿主机文件的场景
hostPath的type字段
| 值 | 行为 |
|---|---|
| 空字符串(默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。 | |
DirectoryOrCreate |
如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权。 |
Directory |
给定的路径下必须存在目录 |
FileOrCreate |
如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。 |
File |
给定的路径下必须存在文件 |
Socket |
给定的路径下必须存在 UNIX 套接字 |
CharDevice |
给定的路径下必须存在字符设备 |
BlockDevice |
给定的路径下必须存在块设备 |
注意事项
- 由于每个节点上的文件都不同,具有相同配置的 pod 在不同节点上的行为可能会有所不同
- 当 Kubernetes 按照计划添加资源感知调度时,将无法考虑
hostPath使用的资源 - 在底层主机上创建的文件或目录只能由 root 写入。您需要在特权容器中以 root 身份运行进程,或修改主机上的文件权限以便写入
hostPath卷
3、示例
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
# directory location on host
path: /data
# this field is optional
type: Directory
2.2.1. 同步文件变化到容器内
hostPath的挂载方式可以挂载目录和文件两种格式,如果使用文件挂载的方式,通过简单的vi等命令修改宿主机的文件,并不会实时同步到容器内的映射文件。而需要对容器进行重启的操作才可以把文件的修改内容同步到文件中,但生产的容器一般不建议执行重启的操作。因此我们可以通过以下的方式来避免这个问题的发生。
以上文件不同步的本质原因是容器在初次挂载的时候使用了宿主机的文件的inode number进行标识,而vi等操作会导致文件的inode number发生变化,所以当宿主机文件的inode number发生变化,容器内并不会发生变化,为了保持文件内容一致,则需要保持修改文件的同时文件的inode number不变。那么我们可以使用 cat 或echo 命令覆盖文件的内容则inode number不会发生变化。
示例:
containers:
volumeMounts:
- mountPath: /etc/hosts
name: hosts
readOnly: true
volumes:
- hostPath:
path: /etc/hosts
type: FileOrCreate
name: hosts
例如,以上的案例是通过挂载宿主机的/etc/hosts文件来映射到容器,如果想修改宿主机的hosts文件来同步容器内的hosts文件,可以通过以下的方式:
# 查看文件的inode
ls -i /etc/hosts
39324780 /etc/hosts
# 追加记录
echo "1.1.1.1 xxx.com" >> /etc/hosts
# 替换内容
sed 's/1.1.1.1/2.2.2.2/g' /etc/hosts > temp.txt
cat temp.txt > /etc/hosts
# 查看宿主机和容器内的inode号都没有发生变化
# crictl exec -it 20891de31a4a6 sh
/var/www/html # ls -i /etc/hosts
39324780 /etc/hosts
2.3. configMap
configMap提供了一种给Pod注入配置文件的方式,配置文件内容存储在configMap对象中,如果Pod使用configMap作为volume的类型,需要先创建configMap的对象。
示例
apiVersion: v1
kind: Pod
metadata:
name: configmap-pod
spec:
containers:
- name: test
image: busybox
volumeMounts:
- name: config-vol
mountPath: /etc/config
volumes:
- name: config-vol
configMap:
name: log-config
items:
- key: log_level
path: log_level
2.4. cephfs
cephfs的方式将Pod的存储挂载到ceph集群中,通过外部存储的方式持久化Pod的数据(即当Pod被删除数据仍可以存储在ceph集群中),前提是先部署和维护好一个ceph集群。
示例
apiVersion: v1
kind: Pod
metadata:
name: cephfs
spec:
containers:
- name: cephfs-rw
image: kubernetes/pause
volumeMounts:
- mountPath: "/mnt/cephfs"
name: cephfs
volumes:
- name: cephfs
cephfs:
monitors:
- 10.16.154.78:6789
- 10.16.154.82:6789
- 10.16.154.83:6789
# by default the path is /, but you can override and mount a specific path of the filesystem by using the path attribute
# path: /some/path/in/side/cephfs
user: admin
secretFile: "/etc/ceph/admin.secret"
readOnly: true
更多可参考 CephFS 示例。
2.5. nfs
nfs的方式类似cephfs,即将Pod数据存储到NFS集群中,具体可参考NFS示例。
2.6. persistentVolumeClaim
persistentVolumeClaim 卷用于将PersistentVolume挂载到容器中。PersistentVolumes 是在用户不知道特定云环境的细节的情况下“声明”持久化存储(例如 GCE PersistentDisk 或 iSCSI 卷)的一种方式。
参考文章:
6.1.2 - PersistentVolume 介绍
1. PV概述
PersistentVolume(简称PV) 是 Volume 之类的卷插件,也是集群中的资源,但独立于Pod的生命周期(即不会因Pod删除而被删除),不归属于某个Namespace。
2. PV和PVC的生命周期
2.1. 配置(Provision)
有两种方式来配置 PV:静态或动态。
1、静态
手动创建PV,可供k8s集群中的对象消费。
2、动态
可以通过StorageClass和具体的Provisioner(例如nfs-client-provisioner)来动态地创建和删除PV。
2.2. 绑定
在动态配置的情况下,用户创建了特定的PVC,k8s会监听新的PVC,并寻找匹配的PV绑定。一旦绑定后,这种绑定是排他性的,PVC和PV的绑定是一对一的映射。
2.3. 使用
Pod 使用PVC作为卷。集群检查PVC以查找绑定的卷并为集群挂载该卷。用户通过在 Pod 的 volume 配置中包含 persistentVolumeClaim 来调度 Pod 并访问用户声明的 PV。
2.4. 回收
PV的回收策略可以设定PVC在释放后如何处理对应的Volume,目前有 Retained, Recycled 和 Deleted三种策略。
1、保留(Retain)
保留策略允许手动回收资源,当删除PVC的时候,PV仍然存在,可以通过以下步骤回收卷:
- 删除PV
- 手动清理外部存储的数据资源
- 手动删除或重新使用关联的存储资产
2、回收(Resycle)
该策略已废弃,推荐使用dynamic provisioning
回收策略会在 volume上执行基本擦除(rm -rf / thevolume / *),可被再次声明使用。
3、删除(Delete)
删除策略,当发生删除操作的时候,会从k8s集群中删除PV对象,并执行外部存储资源的删除操作(根据不同的provisioner定义的删除逻辑不同,有的是重命名)。
动态配置的卷继承其StorageClass的回收策略,默认为Delete,即当用户删除PVC的时候,会自动执行PV的删除策略。
如果要修改PV的回收策略,可执行以下命令:
# Get pv
kubectl get pv
# Change policy to Retaion
kubectl patch pv <pv_name> -p ‘{“spec”:{“persistentVolumeReclaimPolicy”:“Retain”}}’
3. PV的类型
PersistentVolume 类型以插件形式实现。以下仅列部分常用类型:
- GCEPersistentDisk
- AWSElasticBlockStore
- NFS
- RBD (Ceph Block Device)
- CephFS
- Glusterfs
4. PV的属性
每个 PV 配置中都包含一个 sepc 规格字段和一个 status 卷状态字段。
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: fuseim.pri/ifs
creationTimestamp: 2018-07-12T06:46:48Z
name: default-test-web-0-pvc-58cf5ec1-859f-11e8-bb61-005056b83985
resourceVersion: "100163256"
selfLink: /api/v1/persistentvolumes/default-test-web-0-pvc-58cf5ec1-859f-11e8-bb61-005056b83985
uid: 59796ba3-859f-11e8-9c50-c81f66bcff65
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 2Gi
volumeMode: Filesystem
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: test-web-0
namespace: default
resourceVersion: "100163248"
uid: 58cf5ec1-859f-11e8-bb61-005056b83985
nfs:
path: /data/nfs-storage/default-test-web-0-pvc-58cf5ec1-859f-11e8-bb61-005056b83985
server: 172.16.201.54
persistentVolumeReclaimPolicy: Delete
storageClassName: managed-nfs-storage
mountOptions:
- hard
- nfsvers=4.1
status:
phase: Bound
4.1. Capacity
给PV设置特定的存储容量,更多 capacity 可参考Kubernetes 资源模型 。
4.2. Volume Mode
volumeMode 的有效值可以是Filesystem或Block。如果未指定,volumeMode 将默认为Filesystem。
4.3. Access Modes
访问模式包括:
ReadWriteOnce——该卷可以被单个节点以读/写模式挂载ReadOnlyMany——该卷可以被多个节点以只读模式挂载ReadWriteMany——该卷可以被多个节点以读/写模式挂载
在命令行中,访问模式缩写为:
- RWO - ReadWriteOnce
- ROX - ReadOnlyMany
- RWX - ReadWriteMany
一个卷一次只能使用一种访问模式挂载,即使它支持很多访问模式。
以下只列举部分常用插件:
| Volume 插件 | ReadWriteOnce | ReadOnlyMany | ReadWriteMany |
|---|---|---|---|
| AWSElasticBlockStore | ✓ | - | - |
| CephFS | ✓ | ✓ | ✓ |
| GCEPersistentDisk | ✓ | ✓ | - |
| Glusterfs | ✓ | ✓ | ✓ |
| HostPath | ✓ | - | - |
| NFS | ✓ | ✓ | ✓ |
| RBD | ✓ | ✓ | - |
| ... | - |
4.4. Class
PV可以指定一个StorageClass来动态绑定PV和PVC,其中通过 storageClassName 属性来指定具体的StorageClass,如果没有指定该属性的PV,它只能绑定到不需要特定类的 PVC。
4.5. Reclaim Policy
回收策略包括:
Retain(保留)——手动回收Recycle(回收)——基本擦除(rm -rf /thevolume/*)Delete(删除)——关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)将被删除
当前,只有 NFS 和 HostPath 支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。
4.6. Mount Options
Kubernetes 管理员可以指定在节点上为挂载持久卷指定挂载选项。
注意:不是所有的持久化卷类型都支持挂载选项。
支持挂载选项常用的类型有:
- GCEPersistentDisk
- AWSElasticBlockStore
- AzureFile
- AzureDisk
- NFS
- RBD (Ceph Block Device)
- CephFS
- Cinder (OpenStack 卷存储)
- Glusterfs
4.7. Phase
PV可以处于以下的某种状态:
Available(可用)——一块空闲资源还没有被任何声明绑定Bound(已绑定)——卷已经被声明绑定Released(已释放)——声明被删除,但是资源还未被集群重新声明Failed(失败)——该卷的自动回收失败
命令行会显示绑定到 PV 的 PVC 的名称。
参考文章:
6.1.3 - PersistentVolumeClaim 介绍
1. PVC概述
PersistentVolumeClaim(简称PVC)是用户存储的请求,PVC消耗PV的资源,可以请求特定的大小和访问模式,需要指定归属于某个Namespace,在同一个Namespace的Pod才可以指定对应的PVC。
当需要不同性质的PV来满足存储需求时,可以使用StorageClass来实现。
每个 PVC 中都包含一个 spec 规格字段和一个 status 声明状态字段。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 8Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In, values: [dev]}
2. PVC的属性
2.1. accessModes
对应存储的访问模式,例如:ReadWriteOnce。
2.2. volumeMode
对应存储的数据卷模式,例如:Filesystem。
2.3. resources
声明可以请求特定数量的资源。相同的资源模型适用于Volume和PVC。
2.4. selector
声明label selector,只有标签与选择器匹配的卷可以绑定到声明。
- matchLabels:volume 必须有具有该值的标签
- matchExpressions:条件列表,通过条件表达式筛选匹配的卷。有效的运算符包括 In、NotIn、Exists 和 DoesNotExist。
2.5. storageClassName
通过storageClassName参数来指定使用对应名字的StorageClass,只有所请求的类与 PVC 具有相同 storageClassName 的 PV 才能绑定到 PVC。
PVC可以不指定storageClassName,或者将该值设置为空,如果打开了准入控制插件,并且指定一个默认的 StorageClass,则PVC会使用默认的StorageClass,否则就绑定到没有StorageClass的 PV上。
之前使用注解
volume.beta.kubernetes.io/storage-class而不是storageClassName属性。这个注解仍然有效,但是在未来的 Kubernetes 版本中不会支持。
3. 将PVC作为Volume
将PVC作为Pod的Volume,PVC与Pod需要在同一个命名空间下,其实Pod的声明如下:
kind: Pod
apiVersion: v1
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: dockerfile/nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim: # 使用PVC
claimName: myclaim
PersistentVolumes 绑定是唯一的,并且由于 PersistentVolumeClaims 是命名空间对象,因此只能在一个命名空间内挂载具有“多个”模式(ROX、RWX)的PVC。
参考文章:
6.1.4 - StorageClass 介绍
1. StorageClass概述
StorageClass提供了一种描述存储类(class)的方法,不同的class可能会映射到不同的服务质量等级和备份策略或其他策略等。
StorageClass 对象中包含 provisioner、parameters 和 reclaimPolicy 字段,当需要动态分配 PersistentVolume 时会使用到。当创建 StorageClass 对象时,设置名称和其他参数,一旦创建了对象就不能再对其更新。也可以为没有申请绑定到特定 class 的 PVC 指定一个默认的 StorageClass 。
StorageClass对象文件
kind: StorageClass
apiVersion: storage.k8s.io/v3
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Retain
mountOptions:
- debug
2. StorageClass的属性
2.1. Provisioner(存储分配器)
Storage class 有一个分配器(provisioner),用来决定使用哪个卷插件分配 PV,该字段必须指定。可以指定内部分配器,也可以指定外部分配器。外部分配器的代码地址为: kubernetes-incubator/external-storage,其中包括NFS和Ceph等。
2.2. Reclaim Policy(回收策略)
可以通过reclaimPolicy字段指定创建的Persistent Volume的回收策略,回收策略包括:Delete 或者 Retain,没有指定默认为Delete。
2.3. Mount Options(挂载选项)
由 storage class 动态创建的 Persistent Volume 将使用 class 中 mountOptions 字段指定的挂载选项。
2.4. 参数
Storage class 具有描述属于 storage class 卷的参数。取决于分配器,可以接受不同的参数。 当参数被省略时,会使用默认值。
例如以下使用Ceph RBD
kind: StorageClass
apiVersion: storage.k8s.io/v3
metadata:
name: fast
provisioner: kubernetes.io/rbd
parameters:
monitors: 30.36.353.305:6789
adminId: kube
adminSecretName: ceph-secret
adminSecretNamespace: kube-system
pool: kube
userId: kube
userSecretName: ceph-secret-user
fsType: ext4
imageFormat: "2"
imageFeatures: "layering"
对应的参数说明
-
monitors:Ceph monitor,逗号分隔。该参数是必需的。 -
adminId:Ceph 客户端 ID,用于在池(ceph pool)中创建映像。 默认是 “admin”。 -
adminSecretNamespace:adminSecret 的 namespace。默认是 “default”。 -
adminSecret:adminId 的 Secret 名称。该参数是必需的。 提供的 secret 必须有值为 “kubernetes.io/rbd” 的 type 参数。 -
pool: Ceph RBD 池. 默认是 “rbd”。 -
userId:Ceph 客户端 ID,用于映射 RBD 镜像(RBD image)。默认与 adminId 相同。 -
userSecretName:用于映射 RBD 镜像的 userId 的 Ceph Secret 的名字。 它必须与 PVC 存在于相同的 namespace 中。该参数是必需的。 提供的 secret 必须具有值为 “kubernetes.io/rbd” 的 type 参数,例如以这样的方式创建:kubectl create secret generic ceph-secret --type="kubernetes.io/rbd" \ --from-literal=key='QVFEQ1pMdFhPUnQrSmhBQUFYaERWNHJsZ3BsMmNjcDR6RFZST0E9PQ==' \ --namespace=kube-system -
fsType:Kubernetes 支持的 fsType。默认:"ext4"。 -
imageFormat:Ceph RBD 镜像格式,”1” 或者 “2”。默认值是 “1”。 -
imageFeatures:这个参数是可选的,只能在你将 imageFormat 设置为 “2” 才使用。 目前支持的功能只是layering。 默认是 ““,没有功能打开。
参考文章:
6.1.5 - Dynamic Volume Provisioning 介绍
Dynamic Volume Provisioning
Dynamic volume provisioning允许用户按需自动创建存储卷,这种方式可以让用户不需要关心存储的复杂性和差别,又可以选择不同的存储类型。
1. 开启Dynamic Provisioning
需要先提前创建StorageClass对象,StorageClass中定义了使用哪个provisioner,并且在provisioner被调用时传入哪些参数,具体可参考StorageClass介绍。
例如:
- 磁盘类存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
- SSD类存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
2. 使用Dynamic Provisioning
创建一个PVC对象,并且在其中storageClassName字段指明需要用到的StorageClass的名称,例如:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim1
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast
resources:
requests:
storage: 30Gi
当使用到PVC的时候会自动创建对应的外部存储,当PVC被删除的时候,会自动销毁(或备份)外部存储。
3. 默认的StorageClass
当没有对应的StorageClass配置时,可以设定默认的StorageClass,需要执行以下操作:
- 在API Server开启
DefaultStorageClassadmission controller 。 - 设置默认的
StorageClass对象。
可以通过添加storageclass.kubernetes.io/is-default-class注解的方式设置某个StorageClass为默认的StorageClass。当用户创建了一个PersistentVolumeClaim,但没有指定storageClassName的时候,会自动将该PVC的storageClassName指向默认的StorageClass。
参考文章:
6.2 - CSI
6.2.1 - csi-cephfs-plugin
1. 编译CSI CephFS plugin
CSI CephFS plugin用来提供CephFS存储卷和挂载存储卷,源码参考:https://github.com/ceph/ceph-csi 。
1.1. 编译二进制
$ make cephfsplugin
1.2. 编译Docker镜像
$ make image-cephfsplugin
2. 配置项
2.1. 命令行参数
| Option | Default value | Description |
|---|---|---|
--endpoint |
unix://tmp/csi.sock |
CSI endpoint, must be a UNIX socket |
--drivername |
csi-cephfsplugin |
name of the driver (Kubernetes: provisioner field in StorageClass must correspond to this value) |
--nodeid |
empty | This node’s ID |
--volumemounter |
empty | default volume mounter. Available options are kernel and fuse. This is the mount method used if volume parameters don’t specify otherwise. If left unspecified, the driver will first probe for ceph-fuse in system’s path and will choose Ceph kernel client if probing failed. |
2.2. volume参数
| Parameter | Required | Description |
|---|---|---|
monitors |
yes | Comma separated list of Ceph monitors (e.g. 192.168.100.1:6789,192.168.100.2:6789,192.168.100.3:6789) |
mounter |
no | Mount method to be used for this volume. Available options are kernel for Ceph kernel client and fuse for Ceph FUSE driver. Defaults to “default mounter”, see command line arguments. |
provisionVolume |
yes | Mode of operation. BOOL value. If true, a new CephFS volume will be provisioned. If false, an existing CephFS will be used. |
pool |
for provisionVolume=true |
Ceph pool into which the volume shall be created |
rootPath |
for provisionVolume=false |
Root path of an existing CephFS volume |
csiProvisionerSecretName, csiNodeStageSecretName |
for Kubernetes | name of the Kubernetes Secret object containing Ceph client credentials. Both parameters should have the same value |
csiProvisionerSecretNamespace, csiNodeStageSecretNamespace |
for Kubernetes | namespaces of the above Secret objects |
2.3. provisionVolume
2.3.1. 管理员密钥认证
当provisionVolume=true时,必要的管理员认证参数如下:
adminID: ID of an admin clientadminKey: key of the admin client
2.3.2. 普通用户密钥认证
当provisionVolume=false时,必要的用户认证参数如下:
userID: ID of a user clientuserKey: key of a user client
参考文章:
6.2.2 - 部署csi-cephfs
0. 说明
要求Kubernetes的版本在1.11及以上,k8s集群必须允许特权Pod(privileged pods),即apiserver和kubelet需要设置--allow-privileged为true。节点的Docker daemon需要允许挂载共享卷。
涉及镜像
- quay.io/k8scsi/csi-provisioner:v0.3.0
- quay.io/k8scsi/csi-attacher:v0.3.0
- quay.io/k8scsi/driver-registrar:v0.3.0
- quay.io/cephcsi/cephfsplugin:v0.3.0
1. 部署RBAC
部署service accounts, cluster roles 和 cluster role bindings,这些可供RBD和CephFS CSI plugins共同使用,他们拥有相同的权限。
$ kubectl create -f csi-attacher-rbac.yaml
$ kubectl create -f csi-provisioner-rbac.yaml
$ kubectl create -f csi-nodeplugin-rbac.yaml
1.1. csi-attacher-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-attacher
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: external-attacher-runner
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments"]
verbs: ["get", "list", "watch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-attacher-role
subjects:
- kind: ServiceAccount
name: csi-attacher
namespace: default
roleRef:
kind: ClusterRole
name: external-attacher-runner
apiGroup: rbac.authorization.k8s.io
1.2. csi-provisioner-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: external-provisioner-runner
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["list", "watch", "create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-provisioner-role
subjects:
- kind: ServiceAccount
name: csi-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: external-provisioner-runner
apiGroup: rbac.authorization.k8s.io
1.3. csi-nodeplugin-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-nodeplugin
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-nodeplugin
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "update"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments"]
verbs: ["get", "list", "watch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-nodeplugin
subjects:
- kind: ServiceAccount
name: csi-nodeplugin
namespace: default
roleRef:
kind: ClusterRole
name: csi-nodeplugin
apiGroup: rbac.authorization.k8s.io
2. 部署CSI sidecar containers
通过StatefulSet的方式部署external-attacher和external-provisioner供CSI CephFS使用。
$ kubectl create -f csi-cephfsplugin-attacher.yaml
$ kubectl create -f csi-cephfsplugin-provisioner.yaml
2.1. csi-cephfsplugin-provisioner.yaml
kind: Service
apiVersion: v1
metadata:
name: csi-cephfsplugin-provisioner
labels:
app: csi-cephfsplugin-provisioner
spec:
selector:
app: csi-cephfsplugin-provisioner
ports:
- name: dummy
port: 12345
---
kind: StatefulSet
apiVersion: apps/v1beta1
metadata:
name: csi-cephfsplugin-provisioner
spec:
serviceName: "csi-cephfsplugin-provisioner"
replicas: 1
template:
metadata:
labels:
app: csi-cephfsplugin-provisioner
spec:
serviceAccount: csi-provisioner
containers:
- name: csi-provisioner
image: quay.io/k8scsi/csi-provisioner:v0.3.0
args:
- "--provisioner=csi-cephfsplugin"
- "--csi-address=$(ADDRESS)"
- "--v=5"
env:
- name: ADDRESS
value: /var/lib/kubelet/plugins/csi-cephfsplugin/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /var/lib/kubelet/plugins/csi-cephfsplugin
volumes:
- name: socket-dir
hostPath:
path: /var/lib/kubelet/plugins/csi-cephfsplugin
type: DirectoryOrCreate
2.2. csi-cephfsplugin-attacher.yaml
kind: Service
apiVersion: v1
metadata:
name: csi-cephfsplugin-attacher
labels:
app: csi-cephfsplugin-attacher
spec:
selector:
app: csi-cephfsplugin-attacher
ports:
- name: dummy
port: 12345
---
kind: StatefulSet
apiVersion: apps/v1beta1
metadata:
name: csi-cephfsplugin-attacher
spec:
serviceName: "csi-cephfsplugin-attacher"
replicas: 1
template:
metadata:
labels:
app: csi-cephfsplugin-attacher
spec:
serviceAccount: csi-attacher
containers:
- name: csi-cephfsplugin-attacher
image: quay.io/k8scsi/csi-attacher:v0.3.0
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /var/lib/kubelet/plugins/csi-cephfsplugin/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /var/lib/kubelet/plugins/csi-cephfsplugin
volumes:
- name: socket-dir
hostPath:
path: /var/lib/kubelet/plugins/csi-cephfsplugin
type: DirectoryOrCreate
3. 部署CSI-CephFS-driver(plugin)
csi-cephfs-plugin 的作用类似nfs-client,部署在所有node节点上,执行ceph的挂载等相关任务。
通过DaemonSet的方式部署,其中包括两个容器:CSI driver-registrar 和 CSI CephFS driver。
$ kubectl create -f csi-cephfsplugin.yaml
3.1. csi-cephfsplugin.yaml
kind: DaemonSet
apiVersion: apps/v1beta2
metadata:
name: csi-cephfsplugin
spec:
selector:
matchLabels:
app: csi-cephfsplugin
template:
metadata:
labels:
app: csi-cephfsplugin
spec:
serviceAccount: csi-nodeplugin
hostNetwork: true
# to use e.g. Rook orchestrated cluster, and mons' FQDN is
# resolved through k8s service, set dns policy to cluster first
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: driver-registrar
image: quay.io/k8scsi/driver-registrar:v0.3.0
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
- "--kubelet-registration-path=$(DRIVER_REG_SOCK_PATH)"
env:
- name: ADDRESS
value: /var/lib/kubelet/plugins/csi-cephfsplugin/csi.sock
- name: DRIVER_REG_SOCK_PATH
value: /var/lib/kubelet/plugins/csi-cephfsplugin/csi.sock
- name: KUBE_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: socket-dir
mountPath: /var/lib/kubelet/plugins/csi-cephfsplugin
- name: registration-dir
mountPath: /registration
- name: csi-cephfsplugin
securityContext:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: quay.io/cephcsi/cephfsplugin:v0.3.0
args :
- "--nodeid=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
- "--v=5"
- "--drivername=csi-cephfsplugin"
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CSI_ENDPOINT
value: unix://var/lib/kubelet/plugins/csi-cephfsplugin/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: plugin-dir
mountPath: /var/lib/kubelet/plugins/csi-cephfsplugin
- name: pods-mount-dir
mountPath: /var/lib/kubelet/pods
mountPropagation: "Bidirectional"
- mountPath: /sys
name: host-sys
- name: lib-modules
mountPath: /lib/modules
readOnly: true
- name: host-dev
mountPath: /dev
volumes:
- name: plugin-dir
hostPath:
path: /var/lib/kubelet/plugins/csi-cephfsplugin
type: DirectoryOrCreate
- name: registration-dir
hostPath:
path: /var/lib/kubelet/plugins/
type: Directory
- name: pods-mount-dir
hostPath:
path: /var/lib/kubelet/pods
type: Directory
- name: socket-dir
hostPath:
path: /var/lib/kubelet/plugins/csi-cephfsplugin
type: DirectoryOrCreate
- name: host-sys
hostPath:
path: /sys
- name: lib-modules
hostPath:
path: /lib/modules
- name: host-dev
hostPath:
path: /dev
4. 确认部署结果
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/csi-cephfsplugin-attacher-0 1/1 Running 0 26s
pod/csi-cephfsplugin-provisioner-0 1/1 Running 0 25s
pod/csi-cephfsplugin-rljcv 2/2 Running 0 24s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/csi-cephfsplugin-attacher ClusterIP 10.104.116.218 <none> 12345/TCP 27s
service/csi-cephfsplugin-provisioner ClusterIP 10.101.78.75 <none> 12345/TCP 26s
...
参考文档:
6.2.3 - 部署cephfs-provisioner
1. 安装cephfs客户端
所有node节点安装cephfs客户端,主要用来和ceph集群挂载使用。
yum install -y ceph-common
2. 部署RBAC
2.1. ClusterRole
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: cephfs-provisioner
namespace: cephfs
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
- apiGroups: [""]
resources: ["services"]
resourceNames: ["kube-dns","coredns"]
verbs: ["list", "get"]
2.2. ClusterRoleBinding
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: cephfs-provisioner
subjects:
- kind: ServiceAccount
name: cephfs-provisioner
namespace: cephfs
roleRef:
kind: ClusterRole
name: cephfs-provisioner
apiGroup: rbac.authorization.k8s.io
2.3. Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cephfs-provisioner
namespace: cephfs
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "get", "delete"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
2.4. RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cephfs-provisioner
namespace: cephfs
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: cephfs-provisioner
subjects:
- kind: ServiceAccount
name: cephfs-provisioner
2.5. ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: cephfs-provisioner
namespace: cephfs
3. 部署 cephfs-provisioner
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: cephfs-provisioner
namespace: cephfs
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: cephfs-provisioner
spec:
containers:
- name: cephfs-provisioner
image: "quay.io/external_storage/cephfs-provisioner:latest"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 64Mi
env:
- name: PROVISIONER_NAME # 与storageclass的provisioner参数相同
value: ceph.com/cephfs
- name: PROVISIONER_SECRET_NAMESPACE # 与rbac的namespace相同
value: cephfs
command:
- "/usr/local/bin/cephfs-provisioner"
args:
- "-id=cephfs-provisioner-1"
serviceAccount: cephfs-provisioner
4. 部署storageclass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cephfs-provisioner-sc
provisioner: ceph.com/cephfs
volumeBindingMode: WaitForFirstConsumer
parameters:
monitors: 192.168.27.43:6789,192.168.27.44:6789,192.168.27.45:6789
adminId: admin
adminSecretName: csi-cephfs-secret
adminSecretNamespace: "kube-csi"
claimRoot: /pvc-volumes
5. 部署statefulset
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cephfs-provisioner-nginx
spec:
serviceName: "nginx"
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest #nginx的镜像
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: "/mnt" #容器里面的挂载目录,该目录挂载到NFS的共享目录上
name: test
volumeClaimTemplates:
- metadata:
name: test
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 2Gi
storageClassName: cephfs-provisioner-sc
6. 日志
6.1. cephfs-provisoner 执行日志
I0327 07:18:19.742239 1 controller.go:987] provision "default/test-cephfs-ngx-wait-22-0" class "cephfs-provisioner-sc": started
I0327 07:18:19.745239 1 event.go:221] Event(v1.ObjectReference{Kind:"PersistentVolumeClaim", Namespace:"default", Name:"test-cephfs-ngx-wait-22-0", UID:"7f6b60d5-5060-11e9-9a9c-c81f66bcff65", APIVersion:"v1", ResourceVersion:"347214256", FieldPath:""}): type: 'Normal' reason: 'Provisioning' External provisioner is provisioning volume for claim "default/test-cephfs-ngx-wait-22-0"
I0327 07:18:23.281277 1 cephfs-provisioner.go:222] successfully created CephFS share &CephFSPersistentVolumeSource{Monitors:[192.168.27.43:6789 192.168.27.44:6789 192.168.27.45:6789],Path:/pvc-volumes/kubernetes/kubernetes-dynamic-pvc-7f7cb62f-5060-11e9-85c0-0adb8ef08100,User:kubernetes-dynamic-user-7f7cb69f-5060-11e9-85c0-0adb8ef08100,SecretFile:,SecretRef:&SecretReference{Name:ceph-kubernetes-dynamic-user-7f7cb69f-5060-11e9-85c0-0adb8ef08100-secret,Namespace:default,},ReadOnly:false,}
I0327 07:18:23.281371 1 controller.go:1087] provision "default/test-cephfs-ngx-wait-22-0" class "cephfs-provisioner-sc": volume "pvc-7f6b60d5-5060-11e9-9a9c-c81f66bcff65" provisioned
I0327 07:18:23.281415 1 controller.go:1101] provision "default/test-cephfs-ngx-wait-22-0" class "cephfs-provisioner-sc": trying to save persistentvvolume "pvc-7f6b60d5-5060-11e9-9a9c-c81f66bcff65"
I0327 07:18:23.284621 1 controller.go:1108] provision "default/test-cephfs-ngx-wait-22-0" class "cephfs-provisioner-sc": persistentvolume "pvc-7f6b60d5-5060-11e9-9a9c-c81f66bcff65" saved
I0327 07:18:23.284723 1 controller.go:1149] provision "default/test-cephfs-ngx-wait-22-0" class "cephfs-provisioner-sc": succeeded
I0327 07:18:23.284810 1 event.go:221] Event(v1.ObjectReference{Kind:"PersistentVolumeClaim", Namespace:"default", Name:"test-cephfs-ngx-wait-22-0", UID:"7f6b60d5-5060-11e9-9a9c-c81f66bcff65", APIVersion:"v1", ResourceVersion:"347214256", FieldPath:""}): type: 'Normal' reason: 'ProvisioningSucceeded' Successfully provisioned volume pvc-7f6b60d5-5060-11e9-9a9c-c81f66bcff65
6.2. debug 日志
I0327 08:08:11.789608 1 controller.go:987] provision "default/test-cephfs-ngx-wait-44-0" class "cephfs-sc-wait": started
I0327 08:08:11.793258 1 event.go:221] Event(v1.ObjectReference{Kind:"PersistentVolumeClaim", Namespace:"default", Name:"test-cephfs-ngx-wait-44-0", UID:"81846859-5067-11e9-9a9c-c81f66bcff65", APIVersion:"v1", ResourceVersion:"347237916", FieldPath:""}): type: 'Normal' reason: 'Provisioning' External provisioner is provisioning volume for claim "default/test-cephfs-ngx-wait-44-0"
E0327 08:08:12.164705 1 cephfs-provisioner.go:158] failed to provision share "kubernetes-dynamic-pvc-76ecdc5a-5067-11e9-9421-2a1b1be1aeef" for "kubernetes-dynamic-user-76ecdcee-5067-11e9-9421-2a1b1be1aeef", err: exit status 1, output: Traceback (most recent call last):
File "/usr/local/bin/cephfs_provisioner", line 364, in <module>
main()
File "/usr/local/bin/cephfs_provisioner", line 358, in main
print cephfs.create_share(share, user, size=size)
File "/usr/local/bin/cephfs_provisioner", line 228, in create_share
volume = self.volume_client.create_volume(volume_path, size=size, namespace_isolated=not self.ceph_namespace_isolation_disabled)
File "/usr/local/bin/cephfs_provisioner", line 112, in volume_client
self._volume_client.connect(None)
File "/lib/python2.7/site-packages/ceph_volume_client.py", line 458, in connect
self.rados.connect()
File "rados.pyx", line 895, in rados.Rados.connect (/home/jenkins-build/build/workspace/ceph-build/ARCH/x86_64/AVAILABLE_ARCH/x86_64/AVAILABLE_DIST/centos7/DIST/centos7/MACHINE_SIZE/huge/release/13.2.1/rpm/el7/BUILD/ceph-13.2.1/build/src/pybind/rados/pyrex/rados.c:9815)
rados.IOError: [errno 5] error connecting to the cluster
W0327 08:08:12.164908 1 controller.go:746] Retrying syncing claim "default/test-cephfs-ngx-wait-44-0" because failures 2 < threshold 15
E0327 08:08:12.164977 1 controller.go:761] error syncing claim "default/test-cephfs-ngx-wait-44-0": failed to provision volume with StorageClass "cephfs-sc-wait": exit status 1
I0327 08:08:12.165974 1 event.go:221] Event(v1.ObjectReference{Kind:"PersistentVolumeClaim", Namespace:"default", Name:"test-cephfs-ngx-wait-44-0", UID:"81846859-5067-11e9-9a9c-c81f66bcff65", APIVersion:"v1", ResourceVersion:"347237916", FieldPath:""}): type: 'Warning' reason: 'ProvisioningFailed' failed to provision volume with StorageClass "cephfs-sc-wait": exit status 1
参考
6.2.4 - FlexVolume介绍
1. FlexVolume介绍
Flexvolume提供了一种扩展k8s存储插件的方式,用户可以自定义自己的存储插件。类似的功能的实现还有CSI的方式。Flexvolume在k8s 1.8+以上版本提供GA功能版本。
2. 使用方式
在每个node节点安装存储插件二进制,该二进制实现flexvolume的相关接口,默认存储插件的存放路径为/usr/libexec/kubernetes/kubelet-plugins/volume/exec/<vendor~driver>/<driver>。
其中vendor~driver的名字需要和pod中flexVolume.driver的字段名字匹配,该字段名字通过/替换~。
例如:
-
path:/usr/libexec/kubernetes/kubelet-plugins/volume/exec/foo~cifs/cifs
-
pod中flexVolume.driver:foo/cifs
3. FlexVolume接口
节点上的存储插件需要实现以下的接口。
3.1. init
<driver executable> init
3.2. attach
<driver executable> attach <json options> <node name>
3.3. detach
<driver executable> detach <mount device> <node name>
3.4. waitforattach
<driver executable> waitforattach <mount device> <json options>
3.5. isattached
<driver executable> isattached <json options> <node name>
3.6. mountdevice
<driver executable> mountdevice <mount dir> <mount device> <json options>
3.7. unmountdevice
<driver executable> unmountdevice <mount device>
3.8. mount
<driver executable> mount <mount dir> <json options>
3.9. unmount
<driver executable> unmount <mount dir>
3.10. 插件输出
{
"status": "<Success/Failure/Not supported>",
"message": "<Reason for success/failure>",
"device": "<Path to the device attached. This field is valid only for attach & waitforattach call-outs>"
"volumeName": "<Cluster wide unique name of the volume. Valid only for getvolumename call-out>"
"attached": <True/False (Return true if volume is attached on the node. Valid only for isattached call-out)>
"capabilities": <Only included as part of the Init response>
{
"attach": <True/False (Return true if the driver implements attach and detach)>
}
}
4. 示例
4.1. pod的yaml文件内容
nginx-nfs.yaml
相关参数为flexVolume.driver等。
apiVersion: v1
kind: Pod
metadata:
name: nginx-nfs
namespace: default
spec:
containers:
- name: nginx-nfs
image: nginx
volumeMounts:
- name: test
mountPath: /data
ports:
- containerPort: 80
volumes:
- name: test
flexVolume:
driver: "k8s/nfs"
fsType: "nfs"
options:
server: "172.16.0.25"
share: "dws_nas_scratch"
4.2. 插件脚本
nfs脚本实现了flexvolume的接口。
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/k8s~nfs/nfs。
#!/bin/bash
# Copyright 2015 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Notes:
# - Please install "jq" package before using this driver.
usage() {
err "Invalid usage. Usage: "
err "\t$0 init"
err "\t$0 mount <mount dir> <json params>"
err "\t$0 unmount <mount dir>"
exit 1
}
err() {
echo -ne $* 1>&2
}
log() {
echo -ne $* >&1
}
ismounted() {
MOUNT=`findmnt -n ${MNTPATH} 2>/dev/null | cut -d' ' -f1`
if [ "${MOUNT}" == "${MNTPATH}" ]; then
echo "1"
else
echo "0"
fi
}
domount() {
MNTPATH=$1
NFS_SERVER=$(echo $2 | jq -r '.server')
SHARE=$(echo $2 | jq -r '.share')
if [ $(ismounted) -eq 1 ] ; then
log '{"status": "Success"}'
exit 0
fi
mkdir -p ${MNTPATH} &> /dev/null
mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null
if [ $? -ne 0 ]; then
err "{ \"status\": \"Failure\", \"message\": \"Failed to mount ${NFS_SERVER}:${SHARE} at ${MNTPATH}\"}"
exit 1
fi
log '{"status": "Success"}'
exit 0
}
unmount() {
MNTPATH=$1
if [ $(ismounted) -eq 0 ] ; then
log '{"status": "Success"}'
exit 0
fi
umount ${MNTPATH} &> /dev/null
if [ $? -ne 0 ]; then
err "{ \"status\": \"Failed\", \"message\": \"Failed to unmount volume at ${MNTPATH}\"}"
exit 1
fi
log '{"status": "Success"}'
exit 0
}
op=$1
if ! command -v jq >/dev/null 2>&1; then
err "{ \"status\": \"Failure\", \"message\": \"'jq' binary not found. Please install jq package before using this driver\"}"
exit 1
fi
if [ "$op" = "init" ]; then
log '{"status": "Success", "capabilities": {"attach": false}}'
exit 0
fi
if [ $# -lt 2 ]; then
usage
fi
shift
case "$op" in
mount)
domount $*
;;
unmount)
unmount $*
;;
*)
log '{"status": "Not supported"}'
exit 0
esac
exit 1
参考:
- https://github.com/kubernetes/community/blob/master/contributors/devel/sig-storage/flexvolume.md
- https://github.com/kubernetes/examples/tree/master/staging/volumes/flexvolume
- https://github.com/kubernetes/examples/blob/master/staging/volumes/flexvolume/nginx-nfs.yaml
- https://github.com/kubernetes/examples/blob/master/staging/volumes/flexvolume/nfs
7 - 资源隔离
7.1 - 资源配额
资源配额(ResourceQuota)
ResourceQuota对象用来定义某个命名空间下所有资源的使用限额,其实包括:
- 计算资源的配额
- 存储资源的配额
- 对象数量的配额
如果集群的总容量小于命名空间的配额总额,可能会产生资源竞争。这时会按照先到先得来处理。 资源竞争和配额的更新都不会影响已经创建好的资源。
1. 启动资源配额
Kubernetes 的众多发行版本默认开启了资源配额的支持。当在apiserver的--admission-control配置中添加ResourceQuota参数后,便启用了。 当一个命名空间中含有ResourceQuota对象时,资源配额将强制执行。
2. 计算资源配额
可以在给定的命名空间中限制可以请求的计算资源(compute resources)的总量。
| 资源名称 | 描述 |
|---|---|
| cpu | 非终止态的所有pod, cpu请求总量不能超出此值。 |
| limits.cpu | 非终止态的所有pod, cpu限制总量不能超出此值。 |
| limits.memory | 非终止态的所有pod, 内存限制总量不能超出此值。 |
| memory | 非终止态的所有pod, 内存请求总量不能超出此值。 |
| requests.cpu | 非终止态的所有pod, cpu请求总量不能超出此值。 |
| requests.memory | 非终止态的所有pod, 内存请求总量不能超出此值。 |
3. 存储资源配额
可以在给定的命名空间中限制可以请求的存储资源(storage resources)的总量。
| 资源名称 | 描述 |
|---|---|
| requests.storage | 所有PVC, 存储请求总量不能超出此值。 |
| persistentvolumeclaims | 命名空间中可以存在的PVC(persistent volume claims)总数。 |
| .storageclass.storage.k8s.io/requests.storage | 和该存储类关联的所有PVC, 存储请求总和不能超出此值。 |
| .storageclass.storage.k8s.io/persistentvolumeclaims | 和该存储类关联的所有PVC,命名空间中可以存在的PVC(persistent volume claims)总数。 |
4. 对象数量的配额
| 资源名称 | 描述 |
|---|---|
| congfigmaps | 命名空间中可以存在的配置映射的总数。 |
| persistentvolumeclaims | 命名空间中可以存在的PVC总数。 |
| pods | 命名空间中可以存在的非终止态的pod总数。如果一个pod的status.phase 是 Failed, Succeeded, 则该pod处于终止态。 |
| replicationcontrollers | 命名空间中可以存在的rc总数。 |
| resourcequotas | 命名空间中可以存在的资源配额(resource quotas)总数。 |
| services | 命名空间中可以存在的服务总数量。 |
| services.loadbalancers | 命名空间中可以存在的服务的负载均衡的总数量。 |
| services.nodeports | 命名空间中可以存在的服务的主机接口的总数量。 |
| secrets | 命名空间中可以存在的secrets的总数量。 |
例如:可以定义pod的限额来避免某用户消耗过多的Pod IPs。
5. 限额的作用域
| 作用域 | 描述 |
|---|---|
| Terminating | 匹配 spec.activeDeadlineSeconds >= 0 的pod |
| NotTerminating | 匹配 spec.activeDeadlineSeconds is nil 的pod |
| BestEffort | 匹配具有最佳服务质量的pod |
| NotBestEffort | 匹配具有非最佳服务质量的pod |
6. request和limit
当分配计算资源时,每个容器可以为cpu或者内存指定一个请求值和一个限度值。可以配置限额值来限制它们中的任何一个值。
如果指定了requests.cpu 或者 requests.memory的限额值,那么就要求传入的每一个容器显式的指定这些资源的请求。如果指定了limits.cpu或者limits.memory,那么就要求传入的每一个容器显式的指定这些资源的限度。
7. 查看和设置配额
# 创建namespace
$ kubectl create namespace myspace
# 创建resourcequota
$ cat <<EOF > compute-resources.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-resources
spec:
hard:
pods: "4"
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
EOF
$ kubectl create -f ./compute-resources.yaml --namespace=myspace
# 查询resourcequota
$ kubectl get quota --namespace=myspace
NAME AGE
compute-resources 30s
# 查询resourcequota的详细信息
$ kubectl describe quota compute-resources --namespace=myspace
Name: compute-resources
Namespace: myspace
Resource Used Hard
-------- ---- ----
limits.cpu 0 2
limits.memory 0 2Gi
pods 0 4
requests.cpu 0 1
requests.memory 0 1Gi
8. 配额和集群容量
资源配额对象与集群容量无关,它们以绝对单位表示。即增加节点的资源并不会增加已经配置的namespace的资源。
参考文章:
7.2 - 资源配额
Pod限额(LimitRange)
ResourceQuota对象是限制某个namespace下所有Pod(容器)的资源限额
LimitRange对象是限制某个namespace单个Pod(容器)的资源限额
LimitRange对象用来定义某个命名空间下某种资源对象的使用限额,其中资源对象包括:Pod、Container、PersistentVolumeClaim。
1. 为namespace配置CPU和内存的默认值
如果在一个拥有默认内存或CPU限额的命名空间中创建一个容器,并且这个容器未指定它自己的内存或CPU的limit, 它会被分配这个默认的内存或CPU的limit。既没有设置pod的limit和request才会分配默认的内存或CPU的request。
1.1. namespace的内存默认值
# 创建namespace
$ kubectl create namespace default-mem-example
# 创建LimitRange
$ cat memory-defaults.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi
defaultRequest:
memory: 256Mi
type: Container
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/memory-defaults.yaml --namespace=default-mem-example
# 创建Pod,未指定内存的limit和request
$ cat memory-defaults-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: default-mem-demo
spec:
containers:
- name: default-mem-demo-ctr
image: nginx
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/memory-defaults-pod.yaml --namespace=default-mem-example
# 查看Pod
$ kubectl get pod default-mem-demo --output=yaml --namespace=default-mem-example
containers:
- image: nginx
imagePullPolicy: Always
name: default-mem-demo-ctr
resources:
limits:
memory: 512Mi
requests:
memory: 256Mi
1.2. namespace的CPU默认值
# 创建namespace
$ kubectl create namespace default-cpu-example
# 创建LimitRange
$ cat cpu-defaults.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-limit-range
spec:
limits:
- default:
cpu: 1
defaultRequest:
cpu: 0.5
type: Container
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/cpu-defaults.yaml --namespace=default-cpu-example
# 创建Pod,未指定CPU的limit和request
$ cat cpu-defaults-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: default-cpu-demo
spec:
containers:
- name: default-cpu-demo-ctr
image: nginx
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/cpu-defaults-pod.yaml --namespace=default-cpu-example
# 查看Pod
$ kubectl get pod default-cpu-demo --output=yaml --namespace=default-cpu-example
containers:
- image: nginx
imagePullPolicy: Always
name: default-cpu-demo-ctr
resources:
limits:
cpu: "1"
requests:
cpu: 500m
1.3 说明
- 如果没有指定pod的
request和limit,则创建的pod会使用LimitRange对象定义的默认值(request和limit) - 如果指定pod的
limit但未指定request,则创建的pod的request值会取limit的值,而不会取LimitRange对象定义的request默认值。 - 如果指定pod的
request但未指定limit,则创建的pod的limit值会取LimitRange对象定义的limit默认值。
默认Limit和request的动机
如果命名空间具有资源配额(ResourceQuota), 它为内存限额(CPU限额)设置默认值是有意义的。 以下是资源配额对命名空间施加的两个限制:
- 在命名空间运行的每一个容器必须有它自己的内存限额(CPU限额)。
- 在命名空间中所有的容器使用的内存总量(CPU总量)不能超出指定的限额。
如果一个容器没有指定它自己的内存限额(CPU限额),它将被赋予默认的限额值,然后它才可以在被配额限制的命名空间中运行。
2. 为namespace配置CPU和内存的最大最小值
2.1. 内存的最大最小值
创建LimitRange
# 创建namespace
$ kubectl create namespace constraints-mem-example
# 创建LimitRange
$ cat memory-constraints.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: mem-min-max-demo-lr
spec:
limits:
- max:
memory: 1Gi
min:
memory: 500Mi
type: Container
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/memory-constraints.yaml --namespace=constraints-mem-example
# 查看LimitRange
$ kubectl get limitrange cpu-min-max-demo --namespace=constraints-mem-example --output=yaml
...
limits:
- default:
memory: 1Gi
defaultRequest:
memory: 1Gi
max:
memory: 1Gi
min:
memory: 500Mi
type: Container
...
# LimitRange设置了最大最小值,但没有设置默认值,也会被自动设置默认值。
创建符合要求的Pod
# 创建符合要求的Pod
$ cat memory-constraints-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: constraints-mem-demo
spec:
containers:
- name: constraints-mem-demo-ctr
image: nginx
resources:
limits:
memory: "800Mi"
requests:
memory: "600Mi"
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/memory-constraints-pod.yaml --namespace=constraints-mem-example
# 查看Pod
$ kubectl get pod constraints-mem-demo --output=yaml --namespace=constraints-mem-example
...
resources:
limits:
memory: 800Mi
requests:
memory: 600Mi
...
创建超过最大内存limit的pod
$ cat memory-constraints-pod-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: constraints-mem-demo-2
spec:
containers:
- name: constraints-mem-demo-2-ctr
image: nginx
resources:
limits:
memory: "1.5Gi" # 超过最大值 1Gi
requests:
memory: "800Mi"
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/memory-constraints-pod-2.yaml --namespace=constraints-mem-example
# Pod创建失败,因为容器指定的limit过大
Error from server (Forbidden): error when creating "docs/tasks/administer-cluster/memory-constraints-pod-2.yaml":
pods "constraints-mem-demo-2" is forbidden: maximum memory usage per Container is 1Gi, but limit is 1536Mi.
创建小于最小内存request的Pod
$ cat memory-constraints-pod-3.yaml
apiVersion: v1
kind: Pod
metadata:
name: constraints-mem-demo-3
spec:
containers:
- name: constraints-mem-demo-3-ctr
image: nginx
resources:
limits:
memory: "800Mi"
requests:
memory: "100Mi" # 小于最小值500Mi
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/memory-constraints-pod-3.yaml --namespace=constraints-mem-example
# Pod创建失败,因为容器指定的内存request过小
Error from server (Forbidden): error when creating "docs/tasks/administer-cluster/memory-constraints-pod-3.yaml":
pods "constraints-mem-demo-3" is forbidden: minimum memory usage per Container is 500Mi, but request is 100Mi.
创建没有指定任何内存limit和request的pod
$ cat memory-constraints-pod-4.yaml
apiVersion: v1
kind: Pod
metadata:
name: constraints-mem-demo-4
spec:
containers:
- name: constraints-mem-demo-4-ctr
image: nginx
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/memory-constraints-pod-4.yaml --namespace=constraints-mem-example
# 查看Pod
$ kubectl get pod constraints-mem-demo-4 --namespace=constraints-mem-example --output=yaml
...
resources:
limits:
memory: 1Gi
requests:
memory: 1Gi
...
容器没有指定自己的 CPU 请求和限制,所以它将从 LimitRange 获取默认的 CPU 请求和限制值。
2.2. CPU的最大最小值
创建LimitRange
# 创建namespace
$ kubectl create namespace constraints-cpu-example
# 创建LimitRange
$ cat cpu-constraints.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-min-max-demo-lr
spec:
limits:
- max:
cpu: "800m"
min:
cpu: "200m"
type: Container
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/cpu-constraints.yaml --namespace=constraints-cpu-example
# 查看LimitRange
$ kubectl get limitrange cpu-min-max-demo-lr --output=yaml --namespace=constraints-cpu-example
...
limits:
- default:
cpu: 800m
defaultRequest:
cpu: 800m
max:
cpu: 800m
min:
cpu: 200m
type: Container
...
创建符合要求的Pod
$ cat cpu-constraints-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: constraints-cpu-demo
spec:
containers:
- name: constraints-cpu-demo-ctr
image: nginx
resources:
limits:
cpu: "800m"
requests:
cpu: "500m"
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/cpu-constraints-pod.yaml --namespace=constraints-cpu-example
# 查看Pod
$ kubectl get pod constraints-cpu-demo --output=yaml --namespace=constraints-cpu-example
...
resources:
limits:
cpu: 800m
requests:
cpu: 500m
...
创建超过最大CPU limit的Pod
$ cat cpu-constraints-pod-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: constraints-cpu-demo-2
spec:
containers:
- name: constraints-cpu-demo-2-ctr
image: nginx
resources:
limits:
cpu: "1.5"
requests:
cpu: "500m"
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/cpu-constraints-pod-2.yaml --namespace=constraints-cpu-example
# Pod创建失败,因为容器指定的CPU limit过大
Error from server (Forbidden): error when creating "docs/tasks/administer-cluster/cpu-constraints-pod-2.yaml":
pods "constraints-cpu-demo-2" is forbidden: maximum cpu usage per Container is 800m, but limit is 1500m.
创建小于最小CPU request的Pod
$ cat cpu-constraints-pod-3.yaml
apiVersion: v1
kind: Pod
metadata:
name: constraints-cpu-demo-4
spec:
containers:
- name: constraints-cpu-demo-4-ctr
image: nginx
resources:
limits:
cpu: "800m"
requests:
cpu: "100m"
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/cpu-constraints-pod-3.yaml --namespace=constraints-cpu-example
# Pod创建失败,因为容器指定的CPU request过小
Error from server (Forbidden): error when creating "docs/tasks/administer-cluster/cpu-constraints-pod-3.yaml":
pods "constraints-cpu-demo-4" is forbidden: minimum cpu usage per Container is 200m, but request is 100m.
创建没有指定任何CPU limit和request的pod
$ cat cpu-constraints-pod-4.yaml
apiVersion: v1
kind: Pod
metadata:
name: constraints-cpu-demo-4
spec:
containers:
- name: constraints-cpu-demo-4-ctr
image: vish/stress
$ kubectl create -f https://k8s.io/docs/tasks/administer-cluster/cpu-constraints-pod-4.yaml --namespace=constraints-cpu-example
# 查看Pod
kubectl get pod constraints-cpu-demo-4 --namespace=constraints-cpu-example --output=yaml
...
resources:
limits:
cpu: 800m
requests:
cpu: 800m
...
容器没有指定自己的 CPU 请求和限制,所以它将从 LimitRange 获取默认的 CPU 请求和限制值。
2.3. 说明
LimitRange 在 namespace 中施加的最小和最大内存(CPU)限制只有在创建和更新 Pod 时才会被应用。改变 LimitRange 不会对之前创建的 Pod 造成影响。
Kubernetes 都会执行下列步骤:
- 如果容器没有指定自己的内存(CPU)请求(request)和限制(limit),系统将会为其分配默认值。
- 验证容器的内存(CPU)请求大于等于最小值。
- 验证容器的内存(CPU)限制小于等于最大值。
参考文章:
-
https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/
-
https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/cpu-default-namespace/
-
https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-constraint-namespace/
-
https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/cpu-constraint-namespace/
-
https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/
7.3 - 资源服务质量
Resource Quality of Service
1. 资源QoS简介
request值表示容器保证可被分配到资源。limit表示容器可允许使用的最大资源。Pod级别的request和limit是其所有容器的request和limit之和。
2. Requests and Limits
Pod可以指定request和limit资源。其中0 <= request <=Node Allocatable & request <= limit <= Infinity。调度是基于request而不是limit,即如果Pod被成功调度,那么可以保证Pod分配到指定的 request的资源。Pod使用的资源能否超过指定的limit值取决于该资源是否可被压缩。
2.1. 可压缩的资源
- 目前只支持CPU
- pod可以保证获得它们请求的CPU数量,它们可能会也可能不会获得额外的CPU时间(取决于正在运行的其他作业)。因为目前CPU隔离是在容器级别而不是pod级别。
2.2. 不可压缩的资源
- 目前只支持内存
- pod将获得它们请求的内存数量,如果超过了它们的内存请求,它们可能会被杀死(如果其他一些pod需要内存),但如果pod消耗的内存小于请求的内存,那么它们将不会被杀死(除非在系统任务或守护进程需要更多内存的情况下)。
3. QoS 级别
在机器资源超卖的情况下(limit的总量大于机器的资源容量),即CPU或内存耗尽,将不得不杀死部分不重要的容器。因此对容器分成了3个QoS的级别:Guaranteed, Burstable, Best-Effort,三个级别的优先级依次递减。
当CPU资源无法满足,pod不会被杀死可能被短暂控制。
内存是不可压缩的资源,当内存耗尽的情况下,会依次杀死优先级低的容器。Guaranteed的级别最高,不会被杀死,除非容器使用量超过limit限值或者资源耗尽,已经没有更低级别的容器可驱逐。
3.1. Guaranteed
所有的容器的limit值和request值被配置且两者相等(如果只配置limit没有request,则request取值于limit)。
例如:
# 示例1
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
memory: 100Mi
# 示例2
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
requests:
cpu: 10m
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 100m
memory: 100Mi
3.2. Burstable
如果一个或多个容器的limit和request值被配置且两者不相等。
例如:
# 示例1
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
requests:
cpu: 10m
memory: 1Gi
name: bar
# 示例2
containers:
name: foo
resources:
limits:
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
# 示例3
containers:
name: foo
resources:
requests:
cpu: 10m
memory: 1Gi
name: bar
3.3. Best-Effort
所有的容器的limit和request值都没有配置。
例如:
containers:
name: foo
resources:
name: bar
resources:
参考文章:
7.4 - Lxcfs资源视图隔离
1. 资源视图隔离
容器中的执行top、free等命令展示出来的CPU,内存等信息是从/proc目录中的相关文件里读取出来的。而容器并没有对/proc,/sys等文件系统做隔离,因此容器中读取出来的CPU和内存的信息是宿主机的信息,与容器实际分配和限制的资源量不同。
/proc/cpuinfo
/proc/diskstats
/proc/meminfo
/proc/stat
/proc/swaps
/proc/uptime
为了实现让容器内部的资源视图更像虚拟机,使得应用程序可以拿到真实的CPU和内存信息,就需要通过文件挂载的方式将cgroup的真实的容器资源信息挂载到容器内/proc下的文件,使得容器内执行top、free等命令时可以拿到真实的CPU和内存信息。
2. Lxcfs简介
lxcfs是一个FUSE文件系统,使得Linux容器的文件系统更像虚拟机。lxcfs是一个常驻进程运行在宿主机上,从而来自动维护宿主机cgroup中容器的真实资源信息与容器内/proc下文件的映射关系。
lxcfs的命令信息如下:
#/usr/local/bin/lxcfs -h
Usage:
lxcfs [-f|-d] -u -l -n [-p pidfile] mountpoint
-f running foreground by default; -d enable debug output
-l use loadavg
-u no swap
Default pidfile is /run/lxcfs.pid
lxcfs -h
lxcfs的源码:https://github.com/lxc/lxcfs
3. Lxcfs原理
lxcfs实现的基本原理是通过文件挂载的方式,把cgroup中容器相关的信息读取出来,存储到lxcfs相关的目录下,并将相关目录映射到容器内的/proc目录下,从而使得容器内执行top,free等命令时拿到的/proc下的数据是真实的cgroup分配给容器的CPU和内存数据。
原理图

映射目录
| 类别 | 容器内目录 | 宿主机lxcfs目录 |
|---|---|---|
| cpu | /proc/cpuinfo | /var/lib/lxcfs/{container_id}/proc/cpuinfo |
| 内存 | /proc/meminfo | /var/lib/lxcfs/{container_id}/proc/meminfo |
| /proc/diskstats | /var/lib/lxcfs/{container_id}/proc/diskstats | |
| /proc/stat | /var/lib/lxcfs/{container_id}/proc/stat | |
| /proc/swaps | /var/lib/lxcfs/{container_id}/proc/swaps | |
| /proc/uptime | /var/lib/lxcfs/{container_id}/proc/uptime | |
| /proc/loadavg | /var/lib/lxcfs/{container_id}/proc/loadavg | |
| /sys/devices/system/cpu/online | /var/lib/lxcfs/{container_id}/sys/devices/system/cpu/online |
4. 使用方式
4.1. 安装lxcfs
环境准备
yum install -y fuse fuse-lib fuse-devel
源码编译安装
git clone git://github.com/lxc/lxcfs
cd lxcfs
./bootstrap.sh
./configure
make
make install
或者通过rpm包安装
wget https://copr-be.cloud.fedoraproject.org/results/ganto/lxc3/epel-7-x86_64/01041891-lxcfs/lxcfs-3.1.2-0.2.el7.x86_64.rpm;
rpm -ivh lxcfs-3.1.2-0.2.el7.x86_64.rpm --force --nodeps
查看是否安装成功
lxcfs -h
4.2. 运行lxcfs
运行lxcfs主要执行两条命令。
sudo mkdir -p /var/lib/lxcfs
sudo lxcfs /var/lib/lxcfs
可以通过systemd运行。
lxcfs.service文件:
cat > /usr/lib/systemd/system/lxcfs.service <<EOF
[Unit]
Description=lxcfs
[Service]
ExecStart=/usr/bin/lxcfs -f /var/lib/lxcfs
Restart=on-failure
#ExecReload=/bin/kill -s SIGHUP $MAINPID
[Install]
WantedBy=multi-user.target
EOF
运行命令
systemctl daemon-reload && systemctl enable lxcfs && systemctl start lxcfs && systemctl status lxcfs
4.3. 挂载容器内/proc下的文件目录
docker run -it --rm -m 256m --cpus 2 \
-v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \
-v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \
-v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \
-v /var/lib/lxcfs/proc/stat:/proc/stat:rw \
-v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \
-v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \
nginx:latest /bin/sh
4.4. 验证容器内CPU和内存
# cpu
grep -c processor /proc/cpuinfo
cat /proc/cpuinfo
# memory
free -g
cat /proc/meminfo
5. 使用k8s集群部署
使用k8s集群部署与systemd部署方式同理,需要解决2个问题:
- 在每个node节点上部署lxcfs常驻进程,lxcfs需要通过镜像来运行,可以通过daemonset来部署。
- 实现将lxcfs维护的目录自动挂载到pod内的
/proc目录。
具体可参考:https://github.com/denverdino/lxcfs-admission-webhook
5.1. lxcfs-image
FROM centos:7 as build
RUN yum -y update
RUN yum -y install fuse-devel pam-devel wget install gcc automake autoconf libtool make
ENV LXCFS_VERSION 3.1.2
RUN wget https://linuxcontainers.org/downloads/lxcfs/lxcfs-$LXCFS_VERSION.tar.gz && \
mkdir /lxcfs && tar xzvf lxcfs-$LXCFS_VERSION.tar.gz -C /lxcfs --strip-components=1 && \
cd /lxcfs && ./configure && make
FROM centos:7
STOPSIGNAL SIGINT
COPY --from=build /lxcfs/lxcfs /usr/local/bin/lxcfs
COPY --from=build /lxcfs/.libs/liblxcfs.so /usr/local/lib/lxcfs/liblxcfs.so
COPY --from=build /lxcfs/lxcfs /lxcfs/lxcfs
COPY --from=build /lxcfs/.libs/liblxcfs.so /lxcfs/liblxcfs.so
COPY --from=build /usr/lib64/libfuse.so.2.9.2 /usr/lib64/libfuse.so.2.9.2
COPY --from=build /usr/lib64/libulockmgr.so.1.0.1 /usr/lib64/libulockmgr.so.1.0.1
RUN ln -s /usr/lib64/libfuse.so.2.9.2 /usr/lib64/libfuse.so.2 && \
ln -s /usr/lib64/libulockmgr.so.1.0.1 /usr/lib64/libulockmgr.so.1
COPY start.sh /
CMD ["/start.sh"]
star.sh
#!/bin/bash
# Cleanup
nsenter -m/proc/1/ns/mnt fusermount -u /var/lib/lxcfs 2> /dev/null || true
nsenter -m/proc/1/ns/mnt [ -L /etc/mtab ] || \
sed -i "/^lxcfs \/var\/lib\/lxcfs fuse.lxcfs/d" /etc/mtab
# Prepare
mkdir -p /usr/local/lib/lxcfs /var/lib/lxcfs
# Update lxcfs
cp -f /lxcfs/lxcfs /usr/local/bin/lxcfs
cp -f /lxcfs/liblxcfs.so /usr/local/lib/lxcfs/liblxcfs.so
# Mount
exec nsenter -m/proc/1/ns/mnt /usr/local/bin/lxcfs /var/lib/lxcfs/
5.2. daemonset
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: lxcfs
labels:
app: lxcfs
spec:
selector:
matchLabels:
app: lxcfs
template:
metadata:
labels:
app: lxcfs
spec:
hostPID: true
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: lxcfs
image: registry.cn-hangzhou.aliyuncs.com/denverdino/lxcfs:3.1.2
imagePullPolicy: Always
securityContext:
privileged: true
volumeMounts:
- name: cgroup
mountPath: /sys/fs/cgroup
- name: lxcfs
mountPath: /var/lib/lxcfs
mountPropagation: Bidirectional
- name: usr-local
mountPath: /usr/local
volumes:
- name: cgroup
hostPath:
path: /sys/fs/cgroup
- name: usr-local
hostPath:
path: /usr/local
- name: lxcfs
hostPath:
path: /var/lib/lxcfs
type: DirectoryOrCreate
5.3. lxcfs-admission-webhook
lxcfs-admission-webhook实现了一个动态的准入webhook,更准确的讲是实现了一个修改性质的webhook,即监听pod的创建,然后对pod执行patch的操作,从而将lxcfs与容器内的目录映射关系植入到pod创建的yaml中从而实现自动挂载。
apiVersion: apps/v1
kind: Deployment
metadata:
name: lxcfs-admission-webhook-deployment
labels:
app: lxcfs-admission-webhook
spec:
replicas: 1
selector:
matchLabels:
app: lxcfs-admission-webhook
template:
metadata:
labels:
app: lxcfs-admission-webhook
spec:
containers:
- name: lxcfs-admission-webhook
image: registry.cn-hangzhou.aliyuncs.com/denverdino/lxcfs-admission-webhook:v1
imagePullPolicy: IfNotPresent
args:
- -tlsCertFile=/etc/webhook/certs/cert.pem
- -tlsKeyFile=/etc/webhook/certs/key.pem
- -alsologtostderr
- -v=4
- 2>&1
volumeMounts:
- name: webhook-certs
mountPath: /etc/webhook/certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: lxcfs-admission-webhook-certs
具体部署参考:install.sh
#!/bin/bash
./deployment/webhook-create-signed-cert.sh
kubectl get secret lxcfs-admission-webhook-certs
kubectl create -f deployment/deployment.yaml
kubectl create -f deployment/service.yaml
cat ./deployment/mutatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh > ./deployment/mutatingwebhook-ca-bundle.yaml
kubectl create -f deployment/mutatingwebhook-ca-bundle.yaml
执行命令
/deployment/install.sh
参考:
8 - 运维指南
8.1 - Kubernetes集群问题排查
1. 查看系统Event事件
kubectl describe pod <PodName> --namespace=<NAMESPACE>
该命令可以显示Pod创建时的配置定义、状态等信息和最近的Event事件,事件信息可用于排错。例如当Pod状态为Pending,可通过查看Event事件确认原因,一般原因有几种:
- 没有可用的Node可调度
- 开启了资源配额管理并且当前Pod的目标节点上恰好没有可用的资源
- 正在下载镜像(镜像拉取耗时太久)或镜像下载失败。
kubectl describe还可以查看其它k8s对象:NODE,RC,Service,Namespace,Secrets。
1.1. Pod
kubectl describe pod <PodName> --namespace=<NAMESPACE>
以下是容器的启动命令非阻塞式导致容器挂掉,被k8s频繁重启所产生的事件。
kubectl describe pod <PodName> --namespace=<NAMESPACE>
Events:
FirstSeen LastSeen Count From SubobjectPath Reason Message
───────── ──────── ───── ──── ───────────── ────── ───────
7m 7m 1 {scheduler } Scheduled Successfully assigned yangsc-1-0-0-index0 to 10.8.216.19
7m 7m 1 {kubelet 10.8.216.19} containers{infra} Pulled Container image "gcr.io/kube-system/pause:0.8.0" already present on machine
7m 7m 1 {kubelet 10.8.216.19} containers{infra} Created Created with docker id 84f133c324d0
7m 7m 1 {kubelet 10.8.216.19} containers{infra} Started Started with docker id 84f133c324d0
7m 7m 1 {kubelet 10.8.216.19} containers{yangsc0} Started Started with docker id 3f9f82abb145
7m 7m 1 {kubelet 10.8.216.19} containers{yangsc0} Created Created with docker id 3f9f82abb145
7m 7m 1 {kubelet 10.8.216.19} containers{yangsc0} Created Created with docker id fb112e4002f4
7m 7m 1 {kubelet 10.8.216.19} containers{yangsc0} Started Started with docker id fb112e4002f4
6m 6m 1 {kubelet 10.8.216.19} containers{yangsc0} Created Created with docker id 613b119d4474
6m 6m 1 {kubelet 10.8.216.19} containers{yangsc0} Started Started with docker id 613b119d4474
6m 6m 1 {kubelet 10.8.216.19} containers{yangsc0} Created Created with docker id 25cb68d1fd3d
6m 6m 1 {kubelet 10.8.216.19} containers{yangsc0} Started Started with docker id 25cb68d1fd3d
5m 5m 1 {kubelet 10.8.216.19} containers{yangsc0} Started Started with docker id 7d9ee8610b28
5m 5m 1 {kubelet 10.8.216.19} containers{yangsc0} Created Created with docker id 7d9ee8610b28
3m 3m 1 {kubelet 10.8.216.19} containers{yangsc0} Started Started with docker id 88b9e8d582dd
3m 3m 1 {kubelet 10.8.216.19} containers{yangsc0} Created Created with docker id 88b9e8d582dd
7m 1m 7 {kubelet 10.8.216.19} containers{yangsc0} Pulling Pulling image "gcr.io/test/tcp-hello:1.0.0"
1m 1m 1 {kubelet 10.8.216.19} containers{yangsc0} Started Started with docker id 089abff050e7
1m 1m 1 {kubelet 10.8.216.19} containers{yangsc0} Created Created with docker id 089abff050e7
7m 1m 7 {kubelet 10.8.216.19} containers{yangsc0} Pulled Successfully pulled image "gcr.io/test/tcp-hello:1.0.0"
6m 7s 34 {kubelet 10.8.216.19} containers{yangsc0} Backoff Back-off restarting failed docker container
1.2. NODE
kubectl describe node 10.8.216.20
[root@FC-43745A-10 ~]# kubectl describe node 10.8.216.20
Name: 10.8.216.20
Labels: kubernetes.io/hostname=10.8.216.20,namespace/bcs-cc=true,namespace/myview=true
CreationTimestamp: Mon, 17 Apr 2017 11:32:52 +0800
Phase:
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
──── ────── ───────────────── ────────────────── ────── ───────
Ready True Fri, 18 Aug 2017 09:38:33 +0800 Tue, 02 May 2017 17:40:58 +0800 KubeletReady kubelet is posting ready status
OutOfDisk False Fri, 18 Aug 2017 09:38:33 +0800 Mon, 17 Apr 2017 11:31:27 +0800 KubeletHasSufficientDisk kubelet has sufficient disk space available
Addresses: 10.8.216.20,10.8.216.20
Capacity:
cpu: 32
memory: 67323039744
pods: 40
System Info:
Machine ID: 723bafc7f6764022972b3eae1ce6b198
System UUID: 4C4C4544-0042-4210-8044-C3C04F595631
Boot ID: da01f2e3-987a-425a-9ca7-1caaec35d1e5
Kernel Version: 3.10.0-327.28.3.el7.x86_64
OS Image: CentOS Linux 7 (Core)
Container Runtime Version: docker://1.13.1
Kubelet Version: v1.1.1-xxx2-13.1+79c90c68bfb72f-dirty
Kube-Proxy Version: v1.1.1-xxx2-13.1+79c90c68bfb72f-dirty
ExternalID: 10.8.216.20
Non-terminated Pods: (6 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
───────── ──── ──────────── ────────── ─────────────── ─────────────
bcs-cc bcs-cc-api-0-0-1364-index0 1 (3%) 1 (3%) 4294967296 (6%) 4294967296 (6%)
bcs-cc bcs-cc-api-0-0-1444-index0 1 (3%) 1 (3%) 4294967296 (6%) 4294967296 (6%)
fw fw-demo2-0-0-1519-index0 1 (3%) 1 (3%) 4294967296 (6%) 4294967296 (6%)
myview myview-api-0-0-1362-index0 1 (3%) 1 (3%) 4294967296 (6%) 4294967296 (6%)
myview myview-api-0-0-1442-index0 1 (3%) 1 (3%) 4294967296 (6%) 4294967296 (6%)
qa-ts-dna ts-dna-console3-0-0-1434-index0 1 (3%) 1 (3%) 4294967296 (6%) 4294967296 (6%)
Allocated resources:
(Total limits may be over 100%, i.e., overcommitted. More info: http://releases.k8s.io/HEAD/docs/user-guide/compute-resources.md)
CPU Requests CPU Limits Memory Requests Memory Limits
──────────── ────────── ─────────────── ─────────────
6 (18%) 6 (18%) 25769803776 (38%) 25769803776 (38%)
No events.
1.3. RC
kubectl describe rc mytest-1-0-0 --namespace=test
[root@FC-43745A-10 ~]# kubectl describe rc mytest-1-0-0 --namespace=test
Name: mytest-1-0-0
Namespace: test
Image(s): gcr.io/test/mywebcalculator:1.0.1
Selector: app=mytest,appVersion=1.0.0
Labels: app=mytest,appVersion=1.0.0,env=ts,zone=inner
Replicas: 1 current / 1 desired
Pods Status: 1 Running / 0 Waiting / 0 Succeeded / 0 Failed
No volumes.
Events:
FirstSeen LastSeen Count From SubobjectPath Reason Message
───────── ──────── ───── ──── ───────────── ────── ───────
20h 19h 9 {replication-controller } FailedCreate Error creating: Pod "mytest-1-0-0-index0" is forbidden: limited to 10 pods
20h 17h 7 {replication-controller } FailedCreate Error creating: pods "mytest-1-0-0-index0" already exists
20h 17h 4 {replication-controller } SuccessfulCreate Created pod: mytest-1-0-0-index0
1.4. NAMESPACE
kubectl describe namespace test
[root@FC-43745A-10 ~]# kubectl describe namespace test
Name: test
Labels: <none>
Status: Active
Resource Quotas
Resource Used Hard
--- --- ---
cpu 5 20
memory 1342177280 53687091200
persistentvolumeclaims 0 10
pods 4 10
replicationcontrollers 8 20
resourcequotas 1 1
secrets 3 10
services 8 20
No resource limits.
1.5. Service
kubectl describe service xxx-containers-1-1-0 --namespace=test
[root@FC-43745A-10 ~]# kubectl describe service xxx-containers-1-1-0 --namespace=test
Name: xxx-containers-1-1-0
Namespace: test
Labels: app=xxx-containers,appVersion=1.1.0,env=ts,zone=inner
Selector: app=xxx-containers,appVersion=1.1.0
Type: ClusterIP
IP: 10.254.46.42
Port: port-dna-tcp-35913 35913/TCP
Endpoints: 10.0.92.17:35913
Port: port-l7-tcp-8080 8080/TCP
Endpoints: 10.0.92.17:8080
Session Affinity: None
No events.
2. 查看容器日志
1、查看指定pod的日志
kubectl logs <pod_name>
kubectl logs -f <pod_name> #类似tail -f的方式查看
2、查看上一个pod的日志
kubectl logs -p <pod_name>
3、查看指定pod中指定容器的日志
kubectl logs <pod_name> -c <container_name>
4、kubectl logs --help
[root@node5 ~]# kubectl logs --help
Print the logs for a container in a pod. If the pod has only one container, the container name is optional.
Usage:
kubectl logs [-f] [-p] POD [-c CONTAINER] [flags]
Aliases:
logs, log
Examples:
# Return snapshot logs from pod nginx with only one container
$ kubectl logs nginx
# Return snapshot of previous terminated ruby container logs from pod web-1
$ kubectl logs -p -c ruby web-1
# Begin streaming the logs of the ruby container in pod web-1
$ kubectl logs -f -c ruby web-1
# Display only the most recent 20 lines of output in pod nginx
$ kubectl logs --tail=20 nginx
# Show all logs from pod nginx written in the last hour
$ kubectl logs --since=1h nginx
3. 查看k8s服务日志
3.1. journalctl
在Linux系统上systemd系统来管理kubernetes服务,并且journal系统会接管服务程序的输出日志,可以通过systemctl status
其中kubernetes组件包括:
| k8s组件 | 涉及日志内容 | 备注 |
|---|---|---|
| kube-apiserver | ||
| kube-controller-manager | Pod扩容相关或RC相关 | |
| kube-scheduler | Pod扩容相关或RC相关 | |
| kubelet | Pod生命周期相关:创建、停止等 | |
| etcd |
3.2. 日志文件
也可以通过指定日志存放目录来保存和查看日志
- --logtostderr=false:不输出到stderr
- --log-dir=/var/log/kubernetes:日志的存放目录
- --alsologtostderr=false:设置为true表示日志输出到文件也输出到stderr
- --v=0:glog的日志级别
- --vmodule=gfs*=2,test*=4:glog基于模块的详细日志级别
4. 常见问题
4.1. Pod状态一直为Pending
kubectl describe <pod_name> --namespace=<NAMESPACE>
查看该POD的事件。
- 正在下载镜像但拉取不下来(镜像拉取耗时太久)[一般都是该原因]
- 没有可用的Node可调度
- 开启了资源配额管理并且当前Pod的目标节点上恰好没有可用的资源
解决方法:
- 查看该POD所在宿主机与镜像仓库之间的网络是否有问题,可以手动拉取镜像
- 删除POD实例,让POD调度到别的宿主机上
4.2. Pod创建后不断重启
kubectl get pods中Pod状态一会running,一会不是,且RESTARTS次数不断增加。
一般原因为容器启动命令不是阻塞式命令,导致容器运行后马上退出。
非阻塞式命令:
- 本身CMD指定的命令就是非阻塞式命令
- 将服务启动方式设置为后台运行
解决方法:
1、将命令改为阻塞式命令(前台运行),例如:zkServer.sh start-foreground
2、java运行程序的启动脚本将 nohup xxx &的nobup和&去掉,例如:
nohup JAVA_HOME/bin/java JAVA_OPTS -cp $CLASSPATH com.cnc.open.processor.Main &
改为:
JAVA_HOME/bin/java JAVA_OPTS -cp $CLASSPATH com.cnc.open.processor.Main
文章参考《Kubernetes权威指南》
8.2 - kubectl工具
8.2.1 - kubectl安装与配置
1. kubectl的安装
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
安装指定版本的kubectl,例如:v1.9.0
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
2. 配置k8s集群环境
2.1. 命令行方式
2.1.1 非安全方式
kubectl config set-cluster k8s --server=http://<url>
kubectl config set-context <NAMESPACE> --cluster=k8s --namespace=<NAMESPACE>
kubectl config use-context <NAMESPACE>
2.1.2 安全方式
kubectl config set-cluster k8s --server=https://<url> --insecure-skip-tls-verify=true
kubectl config set-credentials k8s-user --username=<username> --password=<password>
kubectl config set-context <NAMESPACE> --cluster=k8s --user=k8s-user --namespace=<NAMESPACE>
kubectl config use-context <NAMESPACE>
2.1.3 查询当前配置环境
[root@test ]# kubectl cluster-info
Kubernetes master is running at http://192.168.10.3:8081
2.2. 添加配置文件的方式
当没有指定 --kubeconfig参数和$KUBECONFIG的环境变量的时候,会默认读取${HOME}/.kube/config。
因此创建${HOME}/.kube/config文件,并在``${HOME}/.kube/ssl`目录下创建ca.pem、cert.pem、key.pem文件。
内容如下:
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
certificate-authority: ./ssl/ca.pem
server: https://192.168.10.3:6443
users:
- name: kubelet
user:
client-certificate: ./ssl/cert.pem
client-key: ./ssl/key.pem
contexts:
- context:
cluster: local
user: kubelet
name: kubelet-cluster.local
current-context: kubelet-cluster.local
3. kubectl config
kubectl config命令说明
$ kubectl config --help
Modify kubeconfig files using subcommands like "kubectl config set current-context my-context"
The loading order follows these rules:
1. If the --kubeconfig flag is set, then only that file is loaded. The flag may only be set once and no merging takes
place.
2. If $KUBECONFIG environment variable is set, then it is used a list of paths (normal path delimitting rules for your
system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a
value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last
file in the list.
3. Otherwise, ${HOME}/.kube/config is used and no merging takes place.
Available Commands:
current-context Displays the current-context
delete-cluster Delete the specified cluster from the kubeconfig
delete-context Delete the specified context from the kubeconfig
get-clusters Display clusters defined in the kubeconfig
get-contexts Describe one or many contexts
rename-context Renames a context from the kubeconfig file.
set Sets an individual value in a kubeconfig file
set-cluster Sets a cluster entry in kubeconfig
set-context Sets a context entry in kubeconfig
set-credentials Sets a user entry in kubeconfig
unset Unsets an individual value in a kubeconfig file
use-context Sets the current-context in a kubeconfig file
view Display merged kubeconfig settings or a specified kubeconfig file
Usage:
kubectl config SUBCOMMAND [options]
Use "kubectl <command> --help" for more information about a given command.
Use "kubectl options" for a list of global command-line options (applies to all commands).
4. shell自动补齐
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> ~/.bashrc
如果出现以下报错
# kubectl自动补齐失败
kubectl _get_comp_words_by_ref : command not found
解决方法:
yum install bash-completion -y
source /etc/profile.d/bash_completion.sh
参考文章:
8.2.2 - kubectl命令使用
1. kubectl命令介绍
kubectl的命令语法
kubectl [command] [TYPE] [NAME] [flags]
其中command,TYPE,NAME,和flags分别是:
-
command: 指定要在一个或多个资源进行操作,例如create,get,describe,delete。 -
TYPE:指定资源类型。资源类型区分大小写,您可以指定单数,复数或缩写形式。例如,以下命令产生相同的输出:kubectl get pod pod1 kubectl get pods pod1 kubectl get po pod1 -
NAME:指定资源的名称。名称区分大小写。如果省略名称,则会显示所有资源的详细信息,比如$ kubectl get pods。按类型和名称指定多种资源:
* 要分组资源,如果它们都是相同的类型:`TYPE1 name1 name2 name<#>`.<br/> 例: `$ kubectl get pod example-pod1 example-pod2` * 要分别指定多种资源类型: `TYPE1/name1 TYPE1/name2 TYPE2/name3 TYPE<#>/name<#>`.<br/> 例: `$ kubectl get pod/example-pod1 replicationcontroller/example-rc1` -
flags:指定可选标志。例如,您可以使用-s或--serverflags来指定Kubernetes API服务器的地址和端口。
更多命令介绍:
[root@node5 ~]# kubectl
kubectl controls the Kubernetes cluster manager.
Find more information at https://github.com/kubernetes/kubernetes.
Basic Commands (Beginner):
create Create a resource from a file or from stdin.
expose Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service
run Run a particular image on the cluster
set Set specific features on objects
run-container Run a particular image on the cluster. This command is deprecated, use "run" instead
Basic Commands (Intermediate):
get Display one or many resources
explain Documentation of resources
edit Edit a resource on the server
delete Delete resources by filenames, stdin, resources and names, or by resources and label selector
Deploy Commands:
rollout Manage the rollout of a resource
rolling-update Perform a rolling update of the given ReplicationController
scale Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job
autoscale Auto-scale a Deployment, ReplicaSet, or ReplicationController
Cluster Management Commands:
certificate Modify certificate resources.
cluster-info Display cluster info
top Display Resource (CPU/Memory/Storage) usage.
cordon Mark node as unschedulable
uncordon Mark node as schedulable
drain Drain node in preparation for maintenance
taint Update the taints on one or more nodes
Troubleshooting and Debugging Commands:
describe Show details of a specific resource or group of resources
logs Print the logs for a container in a pod
attach Attach to a running container
exec Execute a command in a container
port-forward Forward one or more local ports to a pod
proxy Run a proxy to the Kubernetes API server
cp Copy files and directories to and from containers.
auth Inspect authorization
Advanced Commands:
apply Apply a configuration to a resource by filename or stdin
patch Update field(s) of a resource using strategic merge patch
replace Replace a resource by filename or stdin
convert Convert config files between different API versions
Settings Commands:
label Update the labels on a resource
annotate Update the annotations on a resource
completion Output shell completion code for the specified shell (bash or zsh)
Other Commands:
api-versions Print the supported API versions on the server, in the form of "group/version"
config Modify kubeconfig files
help Help about any command
plugin Runs a command-line plugin
version Print the client and server version information
Use "kubectl <command> --help" for more information about a given command.
Use "kubectl options" for a list of global command-line options (applies to all commands).
2. 操作的常用资源对象
- Node
- Podes
- Replication Controllers
- Services
- Namespace
- Deployment
- StatefulSet
具体对象类型及缩写:
* all
* certificatesigningrequests (aka 'csr')
* clusterrolebindings
* clusterroles
* componentstatuses (aka 'cs')
* configmaps (aka 'cm')
* controllerrevisions
* cronjobs
* customresourcedefinition (aka 'crd')
* daemonsets (aka 'ds')
* deployments (aka 'deploy')
* endpoints (aka 'ep')
* events (aka 'ev')
* horizontalpodautoscalers (aka 'hpa')
* ingresses (aka 'ing')
* jobs
* limitranges (aka 'limits')
* namespaces (aka 'ns')
* networkpolicies (aka 'netpol')
* nodes (aka 'no')
* persistentvolumeclaims (aka 'pvc')
* persistentvolumes (aka 'pv')
* poddisruptionbudgets (aka 'pdb')
* podpreset
* pods (aka 'po')
* podsecuritypolicies (aka 'psp')
* podtemplates
* replicasets (aka 'rs')
* replicationcontrollers (aka 'rc')
* resourcequotas (aka 'quota')
* rolebindings
* roles
* secrets
* serviceaccounts (aka 'sa')
* services (aka 'svc')
* statefulsets (aka 'sts')
* storageclasses (aka 'sc')
3. kubectl命令分类[command]
3.1 增
1)create:[Create a resource by filename or stdin]
2)run:[ Run a particular image on the cluster]
3)apply:[Apply a configuration to a resource by filename or stdin]
4)proxy:[Run a proxy to the Kubernetes API server ]
3.2 删
1)delete:[Delete resources ]
3.3 改
1)scale:[Set a new size for a Replication Controller]
2)exec:[Execute a command in a container]
3)attach:[Attach to a running container]
4)patch:[Update field(s) of a resource by stdin]
5)edit:[Edit a resource on the server]
6) label:[Update the labels on a resource]
7)annotate:[Auto-scale a replication controller]
8)replace:[Replace a resource by filename or stdin]
9)config:[config modifies kubeconfig files]
3.4 查
1)get:[Display one or many resources]
2)describe:[Show details of a specific resource or group of resources]
3)log:[Print the logs for a container in a pod]
4)cluster-info:[Display cluster info]
5) version:[Print the client and server version information]
6)api-versions:[Print the supported API versions]
4. Pod相关命令
4.1 查询Pod
kubectl get pod -o wide --namespace=<NAMESPACE>
4.2 进入Pod
kubectl exec -it <PodName> /bin/bash --namespace=<NAMESPACE>
# 进入Pod中指定容器
kubectl exec -it <PodName> -c <ContainerName> /bin/bash --namespace=<NAMESPACE>
4.3 删除Pod
kubectl delete pod <PodName> --namespace=<NAMESPACE>
# 强制删除Pod,当Pod一直处于Terminating状态
kubectl delete pod <PodName> --namespace=<NAMESPACE> --force --grace-period=0
# 删除某个namespace下某个类型的所有对象
kubectl delete deploy --all --namespace=test
4.4 日志查看
$ 查看运行容器日志
kubectl logs <PodName> --namespace=<NAMESPACE>
$ 查看上一个挂掉的容器日志
kubectl logs <PodName> -p --namespace=<NAMESPACE>
5. 常用命令
5.1. Node隔离与恢复
说明:Node设置隔离之后,原先运行在该Node上的Pod不受影响,后续的Pod不会调度到被隔离的Node上。
1. Node隔离
# cordon命令
kubectl cordon <NodeName>
# 或者
kubectl patch node <NodeName> -p '{"spec":{"unschedulable":true}}'
2. Node恢复
# uncordon
kubectl uncordon <NodeName>
# 或者
kubectl patch node <NodeName> -p '{"spec":{"unschedulable":false}}'
5.2. kubectl label
1. 固定Pod到指定机器
kubectl label node <NodeName> namespace/<NAMESPACE>=true
2. 取消Pod固定机器
kubectl label node <NodeName> namespace/<NAMESPACE>-
5.3. 升级镜像
# 升级镜像
kubectl set image deployment/nginx nginx=nginx:1.15.12 -n nginx
# 查看滚动升级情况
kubectl rollout status deployment/nginx -n nginx
5.4. 调整资源值
# 调整指定容器的资源值
kubectl set resources sts nginx-0 -c=agent --limits=memory=512Mi -n nginx
5.5. 调整readiness probe
# 批量查看readiness probe timeoutSeconds
kubectl get statefulset -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.template.spec.containers[0].readinessProbe.timeoutSeconds}{"\n"}{end}'
# 调整readiness probe timeoutSeconds参数
kubectl patch statefulset nginx-sts --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/timeoutSeconds", "value":5}]' -n nginx
5.6. 调整tolerations属性
kubectl patch statefulset nginx-sts --patch '{"spec": {"template": {"spec": {"tolerations": [{"effect": "NoSchedule","key": "dedicated","operator": "Equal","value": "nginx"}]}}}}' -n nginx
5.7. 查看所有节点的IP
kubectl get nodes -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.addresses[0].address}{"\n"}{end}'
5.8. 查看当前k8s组件leader节点
当k8s集群高可用部署的时候,kube-controller-manager和kube-scheduler只能一个服务处于实际逻辑运行状态,通过参数--leader-elect=true来开启选举操作。以下提供查询leader节点的命令。
$ kubectl get endpoints kube-controller-manager --namespace=kube-system -o yaml
apiVersion: v1
kind: Endpoints
metadata:
annotations:
control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"xxx.xxx.xxx.xxx_6537b938-7f5a-11e9-8487-00220d338975","leaseDurationSeconds":15,"acquireTime":"2019-05-26T02:03:18Z","renewTime":"2019-05-26T02:06:08Z","leaderTransitions":1}'
creationTimestamp: "2019-05-26T01:52:39Z"
name: kube-controller-manager
namespace: kube-system
resourceVersion: "1965"
selfLink: /api/v1/namespaces/kube-system/endpoints/kube-controller-manager
uid: f1755fc5-7f58-11e9-b4c4-00220d338975
以上表示"holderIdentity":"xxx.xxx.xxx.xxx为kube-controller-manager的leader节点。
同理,可以通过以下命令查看kube-scheduler的leader节点。
kubectl get endpoints kube-scheduler --namespace=kube-system -o yaml
5.9. 修改副本数
kubectl scale deployment.v1.apps/nginx-deployment --replicas=10
5.10. 批量删除pod
kubectl get po -n default |grep Evicted |awk '{print $1}' |xargs -I {} kubectl delete po {} -n default
5.11. 各种查看命令
# 不使用外部工具来输出解码后的 Secret
kubectl get secret my-secret -o go-template='{{range $k,$v := .data}}{{"### "}}{{$k}}{{"\n"}}{{$v|base64decode}}{{"\n\n"}}{{end}}'
# 列出事件(Events),按时间戳排序
kubectl get events --sort-by=.metadata.creationTimestamp
5.12. 拷贝文件
从pod拷贝到本地
注意事项:
-
pod的目录是workdir的相对路径,可以将文件拷贝到workdir下再拷贝出来
-
文件绝对路径前面不能加 /
-
文件目标位置不能为文件夹,必须为文件路径
kubectl cp -n <ns> -c <container> <pod_name>:<与workdir的相对路径> <本地路径文件名>
# 示例:
# 将pod workdir下的prometheus.env.yaml文件拷贝到本地
kubectl cp -n prometheus -c prometheus prometheus-0:prometheus.env.yaml ./prometheus.env.yaml
从本地拷贝到pod
注意事项:
- 如果没有加路径,默认拷贝到pod内workdir路径。
kubectl cp <本地路径文件名> -n <ns> -c <container> <pod_name>:<与workdir的相对路径>
# 示例:
kubectl cp ./prometheus.env.yaml -n prometheus -c prometheus prometheus-0:prometheus.env.yaml
5.13. 强制删除namespace
如果强制删除ns失败,可以使用以下命令删除,将以下的calico-system改为需要删除的namespace。
kubectl get namespaces calico-system -o json \
| tr -d "\n" | sed "s/\"finalizers\": \[[^]]\+\]/\"finalizers\": []/" \
| kubectl replace --raw /api/v1/namespaces/calico-system/finalize -f -
5.14. edit status
参考:
- https://github.com/ulucinar/kubectl-edit-status
- https://krew.sigs.k8s.io/docs/user-guide/setup/install/
1、通过二进制安装
version=v0.3.0
wget https://github.com/ulucinar/kubectl-edit-status/releases/download/${version}/kubectl-edit-status_${version}_linux_amd64.tar.gz
tar -zvxf kubectl-edit-status_${version}_linux_amd64.tar.gz
cp kubectl-edit_status /usr/bin/
2、通过krew安装edit-status
安装krew
(
set -x; cd "$(mktemp -d)" &&
OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
KREW="krew-${OS}_${ARCH}" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
tar zxvf "${KREW}.tar.gz" &&
./"${KREW}" install krew
)
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
通过krew安装edit-status
说明:edit-status一般在该status是终止状态时可以修改,如果非终止状态可能被operator recycle状态覆盖。
kubectl krew update
kubectl krew install edit-status
6. kubectl日志级别
Kubectl 日志输出详细程度是通过 -v 或者 --v 来控制的,参数后跟一个数字表示日志的级别。 Kubernetes 通用的日志习惯和相关的日志级别在 这里 有相应的描述。
| 详细程度 | 描述 |
|---|---|
--v=0 |
用于那些应该 始终 对运维人员可见的信息,因为这些信息一般很有用。 |
--v=1 |
如果您不想要看到冗余信息,此值是一个合理的默认日志级别。 |
--v=2 |
输出有关服务的稳定状态的信息以及重要的日志消息,这些信息可能与系统中的重大变化有关。这是建议大多数系统设置的默认日志级别。 |
--v=3 |
包含有关系统状态变化的扩展信息。 |
--v=4 |
包含调试级别的冗余信息。 |
--v=5 |
跟踪级别的详细程度。 |
--v=6 |
显示所请求的资源。 |
--v=7 |
显示 HTTP 请求头。 |
--v=8 |
显示 HTTP 请求内容。 |
--v=9 |
显示 HTTP 请求内容而且不截断内容。 |
参考文章:
8.2.3 - kubectl命令别名
1. kubectl-aliases
kubectl-aliases开源工具是由脚本通过拼接各种kubectl相关元素组成的alias命令别名列表,其中命令别名拼接元素如下:
| base | [system?] | [operation] | [resource] | [flags] |
|---|---|---|---|---|
kubectl |
-n=kube-system |
getdescribe rm:deletelogsexecapply |
podsdeploymentsecretingressnode svcns cm |
oyaml ojsonowide allwatchfilel |
k=kubectl- sys=
--namespace kube-system
- sys=
- commands:
- g=
get - d=
describe - rm=
delete - a:
apply -f - ex:
exec -i -t - lo:
logs -f
- g=
- resources:
- po=
pod - dep=
deployment - ing=
ingress - svc=
service - cm=
configmap - sec=
secret - ns=
namespace - no=
node
- po=
- flags:
- output format: oyaml, ojson, owide
- all:
--allor--all-namespacesdepending on the command - sl:
--show-labels - w=
-w/--watch
- value flags (should be at the end):
- f=
-f/--filename - l=
-l/--selector
- f=
2. 示例
# 示例1
kd → kubectl describe
# 示例2
kgdepallw → kubectl get deployment —all-namespaces —watch
alias get示例:
alias k='kubectl'
alias kg='kubectl get'
alias kgpo='kubectl get pods'
alias kgpoojson='kubectl get pods -o=json'
alias kgpon='kubectl get pods --namespace'
alias ksysgpooyamll='kubectl --namespace=kube-system get pods -o=yaml -l'
3. 安装
# 将 .kubectl_aliases下载到 home 目录
cd ~ && wget https://raw.githubusercontent.com/ahmetb/kubectl-aliases/master/.kubectl_aliases
# 将以下内容添加到 .bashrc中,并执行 source .bashrc
[ -f ~/.kubectl_aliases ] && source ~/.kubectl_aliases
function kubectl() { command kubectl $@; }
# 如果需要提示别名的完整命令,则将以下内容添加到 .bashrc中,并执行 source .bashrc
[ -f ~/.kubectl_aliases ] && source ~/.kubectl_aliases
function kubectl() { echo "+ kubectl $@"; command kubectl $@; }
参考:
8.2.4 - kubectl进入node shell
本文介绍如何通过kubectl进入节点的shell环境。
1. 安装krew node-shell
1.1. 安装krew
(
set -x; cd "$(mktemp -d)" &&
OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
KREW="krew-${OS}_${ARCH}" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
tar zxvf "${KREW}.tar.gz" &&
./"${KREW}" install krew
)
在~/.bashrc或~/.zshrc添加以下命令
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
1.2. 安装node-shell
node-shell的代码参考:kubectl-node-shell/kubectl-node_shell at master · kvaps/kubectl-node-shell · GitHub
kubectl krew install node-shell
示例:
# kubectl krew install node-shell
Updated the local copy of plugin index.
Installing plugin: node-shell
Installed plugin: node-shell
\
| Use this plugin:
| kubectl node-shell
| Documentation:
| https://github.com/kvaps/kubectl-node-shell
| Caveats:
| \
| | You need to be allowed to start privileged pods in the cluster
| /
/
WARNING: You installed plugin "node-shell" from the krew-index plugin repository.
These plugins are not audited for security by the Krew maintainers.
Run them at your own risk.
2. 进入节点的shell
2.1. 登录node
创建一个临时的特权容器,登录容器即登录node shell。
kubectl node-shell <node-name>
示例:
# kubectl node-shell node1
spawning "nsenter-9yqytp" on "node1"
If you don't see a command prompt, try pressing enter.
groups: cannot find name for group ID 11
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
root@node1:/#
2.2. 退出node
退出容器,容器会被自动删除。
# exit
logout
pod default/nsenter-9yqytp terminated (Error)
pod "nsenter-9yqytp" deleted
3. 原理
容器是弱隔离,共享节点的内核,通过cgroup和namespace来实现进程级别的隔离。那么通过在特权容器里执行nsenter的命令,则可以通过登录特权容器来实现登录node的shell环境。
创建一个特权容器,进入node shell的命令为:
nsenter --target 1 --mount --uts --ipc --net --pid -- bash -l
进入 node shell 的权限:
-
hostPID: true共享 host 的 pid -
hostNetwork: true共享 host 的网络 -
privileged: true: PSP 权限策略是privileged, 即完全无限制。
3.1. Pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nsenter-9yqytp
name: nsenter-9yqytp
namespace: default
spec:
containers:
- command:
- nsenter
- --target
- "1"
- --mount
- --uts
- --ipc
- --net
- --pid
- --
- bash
- -l
image: docker.io/library/alpine
imagePullPolicy: Always
name: nsenter
resources:
limits:
cpu: 100m
memory: 256Mi
requests:
cpu: 100m
memory: 256Mi
securityContext:
privileged: true
stdin: true
stdinOnce: true
tty: true
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-4ktlf
readOnly: true
enableServiceLinks: true
hostNetwork: true
hostPID: true
nodeName: node1
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Never
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
tolerations:
- key: CriticalAddonsOnly
operator: Exists
- effect: NoExecute
operator: Exists
volumes:
- name: kube-api-access-4ktlf
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
创建完容器后,直接登录容器即可登录节点的shell
kubectl exec -it nsenter-9yqytp bash
参考:
8.2.5 - kubeconfig的使用
1. kubeconfig说明
默认情况下,kubectl 在 $HOME/.kube 目录下查找名为 config 的文件。 你可以通过设置 KUBECONFIG 环境变量或者设置 --kubeconfig参数来指定其他 kubeconfig 文件。
kubeconfig内容示例:
以下证书以文件的形式读取。
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: */ca.crt
server: https://******
name: demo
contexts:
- context:
cluster: demo
user: demo
name: demo
current-context: demo
preferences: {}
users:
- name: demo
user:
client-certificate: */client.crt
client-key: */client.key
2. 将 Kubeconfig 中的证书文件转为证书数据
2.1. 证书文件转为证书数据
如果需要将证书文件替换为证书数据格式,可以通过base64的命令解码成data数据,同时修改相应的参数。
获取证书文件的 base64 编码:
cat "证书文件" | base64
将输出结果的换行符去掉,填到对应证书的data中。
-
将
certificate-authority改为certificate-authority-data,并且将*/ca.crt证书文件经 base64 编码后的字符串填入该位置。 -
将
client-certificate改为client-certificate-data,并且将*/client.crt证书文件经 base64 编码后的字符串填入该位置。 -
将
client-key改为client-key-data,并且将*/client.key证书文件 base64 编码后的字符串填入该位置。
例如:
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZADQFURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJd01ESXhNREEwTURJMU1Wb1hEVE13TURJd09EQTBNREkxTVZvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUc1CjYwU0txcHVXeE1mWlVabmVFakM5bjFseHFQSzdUTlVQbzROejFWcWxaQkt6NzJDVVErZjBtVGNQLy9oS3BQUVAKaG9pNndyaXJRUmVERTErRFIrOTZHVDIrSGZ3L2VHQTI5ZmErNS80UG5PWlpTUEVpS3MxVVdhc0VqSDJVZG4xTwpEejVRZk1ESkFjZlBoTzV0eUZFaGZNa2hid0Y2QkJONnh5RmJJdXl4OThmZGx5SWJJUnpLSml6VWZQcUx2WUZoCmFQbjF4WFZyT2QyMnFtblgzL2VxZXM4aG51SmpJdlVPbWRDRlhjQVRYdE00Wmw2bERvWUs2VS9vaEFzM0x4VzAKWUV4ZkcxMzFXdjIrR0t4WWV2Q0FuMitSQ3NBdFpTZk9zcVljMmorYS9FODVqdzcySlFkNGd6eGlHMCszaU14WApWaGhpcWFrY1owZlRCc0FtZHY4Q0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFDKzFuU2w0dnJDTEV6eWg0VWdXR3ZWSldtV2ltM2dBWFFJU1R2WG56NXZqOXE3Z0JYSwpCRVUyakVHTFF2UEJQWUZwUjhmZllCZCtqT2xtYS9IdU9ISmw0RUxhaHJKbnIwaU9YcytoeVlpV0ZUKzZ2R05RCmY4QnAvNTlkYzY1ejVVMnlUQjd4VkhMcGYzRTRZdUN2NmZhdy9PZTNUUzZUbThZdFBXREgxNDBOR2ZKMHlWRlYKSzZsQnl5THMwMzZzT1V5ZUJpcEduOUxyKytvb09mTVZIU2dpaEJlcEl3ZVVvYk05YU1ram1Hb2VjNk5HTUN3NwpkaFNWTmdMNGxMSnRvRktoVDdTZHFjMmk2SWlwbkJrdUlHUWRJUFliQnF6MkN5eVMyRkZmeEJsV2lmNmcxMTFTClphSUlpQ0lLbXNqeDJvTFBhOUdNSjR6bERNR1hLY1ZyNnhhVQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
server: https://******
name: demo
contexts:
- context:
cluster: demo
user: demo
name: demo
current-context: demo
preferences: {}
users:
- name: demo
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURBRENDQWVpZ0F3SUJBZ0lCQWpBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJd01ESXhNekV5TXpreU5sb1hEVEl4TURJeE16RXlNemt5Tmxvd01URVhNQlVHQTFVRQpDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGakFVQmdOVkJBTVREVzFwYm1scmRXSmxMWFZ6WlhJd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDdEp5MThYOGVBaVlJc1g3Z0xQazBOZEFuRlNJU0kKeXNGMGIzS21CTk9VclRIcGtnaUFwRldmZDJaVXNZR3Iwd0VBL0FIWm9PMUxPUHdqYzEyb2o1bGIwWlNNWTlMaQpVeW9UQ3huREZIVDJIQnlPNCtGRk5pVCtXTU5FTURURWxERXhlano1WVIwb1pZVjN2V2Z5T3l2SlBna1dFU29IClVVcnVvQmRRbU5LUzhCeXhIUmFvcnFJUFRVRGkzUFJIbTlGZERKV1lNTWpnbDZmbmdHWkRQMnpjTlNFZjRGNzYKc2FUK0VhMU1kbjV5akRCNXczaHJvZXBBclc0QVUyR3NTRFZyTHY2UDFiSWV0RDdONjZrT1BBNklIUlRKbUNLLwp2aEJrbGVPMGFwcERzTERDT3hpMkc2Q1BRNDZVYVZUOEhZMk9sQU5nSmtRRDNwYjFXTnlhQlVpRkFnTUJBQUdqClB6QTlNQTRHQTFVZER3RUIvd1FFQXdJRm9EQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBTHVrZ1dxWnRtbkZITlcxYgpqL012ekcxTmJyUEtWYXVobml5RzRWWnRZYzR1Uk5iSGhicEhEdThyamc2dVB1Y0xMdHAzblU2UGw4S2J2WFpiCmplNmJQR2xvV3VBcFIrVW9KRFQ2VEpDK2o2Qm5CSXpWQkNOL21lSWVPQ0hEK1k5L2dtbzRnd2Q4c2F3U0Z1bjMKZTFVekF2cHBwdTVZY05wcU92aUkxT2NjNGdxNTd2V1h1MFRIdUJkM0VtQ2JZRXUzYXhOL25ldnhOYnYxbDFRSQovSzRaOWw3MXFqaEp3SVlBaHUzek5pTWpCU1VTRjJkZnd2NmFnclhSUnN6b1Z4ejE5Mm9qM2pWU215cXZxeVFrCmZXckpsc3VhY1NDdTlKUE44OUQrVXkwVnZXZmhPdmp4cXVRSktwUW9hMzlQci81Q3YweXFKUkFIMkk5Wk1IZEYKNkJQRVBRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVQSLS0tLQpNSUlFcEFJQkFBS0NBUUVBclNjdGZGL0hnSW1DTEYrNEN6NU5EWFFKeFVpRWlNckJkRzl5cGdUVGxLMHg2WklJCmdLUlZuM2RtVkxHQnE5TUJBUHdCMmFEdFN6ajhJM05kcUkrWlc5R1VqR1BTNGxNcUV3c1p3eFIwOWh3Y2p1UGgKUlRZay9sakRSREEweEpReE1YbzgrV0VkS0dXRmQ3MW44anNyeVQ0SkZoRXFCMUZLN3FBWFVKalNrdkFjc1IwVwpxSzZpRDAxQTR0ejBSNXZSWFF5Vm1EREk0SmVuNTRCbVF6OXMzRFVoSCtCZStyR2svaEd0VEhaK2Nvd3dlY040CmE2SHFRSzF1QUZOaHJFZzFheTcrajlXeUhyUSt6ZXVwRGp3T2lCMFV5Wmdpdjc0UVpKWGp0R3FhUTdDd3dqc1kKdGh1Z2owT09sR2xVL0IwdGpwUURZQ1pFQTk2VzlWamNtZ1ZJaFFJREFRQUJBb0lCQUdDazVGTnVGaWtkRndYegphd01EZy9oRlV3ckZIZ3hIdHRCcFFBRi80aVF5d3hBT0RTYllFbDVPUTFSME90OFBoNWpvRDVSTHFRWjZTT2owCmhFc0gwMTRYVFNWS3RqTFNua0pBeU9GRWNyL0hFdjJDSFlNRzVJRCtSQWEwTFUrbk13bmRvMWpCcG9lY21uRXAKeTNHOUt3Ukkxc04xVXhNQWdhVk12NWFocGE2UzRTdENpalh3VGVVWUxpc1pSZGp5UGljUWlQN0xaSnhBcjRLTgpTUHlDNE1IZTJtV3F3cjM5cnBrMWZ3WkViMTRPMjR2Z3dMYmROTFJYdVhZSTdicEpOUGRJbEQvRExOQkJSL0FVCjhJYjNDaTZwZ2M4dFA4VzJCeW9TQUJVZUNpWDRFM21wQUttVytKbzFuU3FwQ1FnM2JGV0RpRjFrKzdEZjJZM3IKc09UT0srVUNnWUVBMTBEb3BtRVcrNnowanZadVFZdFlPWlVQQzZwV0dBaDFLWlVHZndYVWVLQ3dnNlNuQW9qRwpuMjR4bWJVdTlzRzBjd2syK0VVcXh3S2IrR2NJVTdyNVdOaUNXNVZYQzV5ZUp3OFZiMWtQekRzMSs4YzA4VjNhCkkzNHluOHpjZm1WTkZOUm1ZS1FMK1FGbnZ3ZXM4NFRleGVBb1hGY2FQcVZTNDNVV2JHRW02ZThDZ1lFQXplNFkKeUxYQ2pWNVppajFsUTdkQ2FweEQvL0dCT2JsemRocXRuSjl1OWxyVkQ4Y3RvUEVKYVkwVFFWc3FaNVhCdTNtVApLb0w4bmZjWHg4cWR3Z3gvZFRHa2c3d00rZFkrUTFuVDNOWnRFSVdVUkR3T0hLT1N1Tm5kdnU1a1Q4aXRrdHhhCktrYVlSMlNOT25iMlFzb1ZHa3F5OS9QK0xWL0FyeWdScmtaYXVNc0NnWUVBaGpUTkdUYzltaXNxeTV2d0FHTzkKM1NFSG9YRlJmbWgvakM2RFAxMUdMUE9iT21qRlREbzFCS0F5d3JBSm1RWUsyUkpzdUh4L2dGY3JJY1F6bCtqaQpvRGRWaDM1a0tEUTlFd00valE0TllIdW1XOVhITjVvWmNMbTFISmNnL3Bsd1pzVkxFNFFVaHVzT1lUZUs2TVgyCkU0OS8rcHJBSFVEOG5oNlpuWGN4U1BjQ2dZRUFub0lQcDZab1MwSjliMi9VbTJ2YS9vNnJ0TDBpOTlpc2JCTWEKNFR6RFAzTXBIc3owYlRZN1JYaW1ncDcybytiY3lUNUtMZVhISnB3RVBPL1R3SUs0Tk8veUxzZzN3TExOR0RCegphRC9Ra1hBUWNQazg3NFJrc2s1WVpkZS9kTDRHQk00QnhScXpxZmhXME5LeXVUUXRUQ0NGWTEvMm5OeGdSekp6CmNZNkwxRU1DZ1lCdXpHRkJVeXM4TFpHekFNTWdmN1VRQ0dVZERrT2dJRHdzd0dxVVlxQ2ZLcnlGYVdCOUJvSi8KMnJMVmVYNDVXTnFpa0tCMlgvckdRbGFIK25YalRBaDlpN0NrWGRySUQzeXA1cGJBa0VnYjg3dGo2Y3hONlBOcQo5cnhzOU1lR0NleFhsdjBkUUpMUkowNXU2OEVxYm44Q0RIOXFRSWZaTml0NXA0S0JFVkp3L1E9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
2.2. 证书数据转成证书文件
grep 'certificate-authority-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d > ca.crt
grep 'client-key-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d > client.key
grep 'client-certificate-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d > client.crt
3. 生成集群级别的kubeconfig
3.1. 创建ServiceAccount
# 1. 创建用户
cat<<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: ${ServiceAccountNS}
name: ${ServiceAccountName}
EOF
# 2. 创建Secret绑定ServiceAccount
# k8s 1.24后的版本不再自动生成secret,绑定后当删除ServiceAccount时会自动删除secret
cat<<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: ${SecretName}
namespace: ${ServiceAccountNS}
annotations:
kubernetes.io/service-account.name: "${ServiceAccountName}"
type: kubernetes.io/service-account-token
EOF
3.2. 创建和绑定权限
如果不创建自定义权限,可以使用自带的ClusterRole: cluster-admin/admin/edit/view。如果需要生成集群admin的权限,可以绑定 cluster-admin的权限。建议权限授权时遵循最小权限原则。即需要使用的权限授权,不需要使用的权限不授权,对于删除等敏感权限慎重授权。
如果要创建自定义权限,使用ClusterRole和ClusterRoleBinding的对象。
-
只读权限:verbs: ["get","list","watch"]
-
可写权限:verbs: ["create","update","patch","delete","deletecollection"]
-
全部权限:verbs: ["*"]
# 3. 创建权限: 可以自定义权限,或者使用clusterrole: cluster-admin/admin/edit/view权限
# 只读权限:verbs: ["get","list","watch"]
# 可写权限:verbs: ["create","update","patch","delete","deletecollection"]
# 全部权限:verbs: ["*"]
cat<<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ${ClusterRoleName}
rules:
- apiGroups: ["*"]
resources: ["pods","deployments"]
verbs: ["*"]
EOF
# 4. 绑定权限
cat<<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ${ClusterRoleBindingName}
subjects:
- kind: ServiceAccount
name: ${ServiceAccountName}
namespace: ${ServiceAccountNS}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ${ClusterRoleName}
EOF
3.3. 创建kubeconfig
# 5. 基于secret获取token
TOKEN=$(kubectl get secret ${SecretName} -n ${ServiceAccountNS} -o jsonpath={".data.token"} | base64 -d)
# 6. 创建kubeconfig文件
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config set-cluster ${KubeConfigCluster} \
--server=https://${APISERVER} \
--certificate-authority=${CaFilePath} \
--embed-certs=true
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config set-credentials ${USER} --token=${TOKEN}
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config set-context ${USER}@${KubeConfigCluster} --cluster=${KubeConfigCluster} --user=${USER}
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config use-context ${USER}@${KubeConfigCluster}
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config view
4. 生成namespace的kubeconfig
4.1. 创建ServiceAccount
创建ServiceAccout同上,没有区别。
4.2. 创建和绑定权限
创建namespace级别的权限用Role和RoleBinding的对象。一个ServiceAccount可以绑定多个权限,可以通过创建ClusterRoleBinding或RoleBinding来实现对多个namespace的权限控制。
# 3. 创建权限: 可以自定义权限,或者使用clusterrole: admin/edit/view权限
cat<<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: ${USER_NAMESPACE}
name: ${RoleName}
rules:
- apiGroups: ["*"]
resources: ["pods"]
verbs: ["*"]
EOF
# 4. 绑定权限
cat<<EOF | kubectl apply -f -
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ${RoleBindingName}
namespace: ${USER_NAMESPACE}
subjects:
- kind: ServiceAccount
name: ${ServiceAccountName}
namespace: ${ServiceAccountNS}
roleRef:
kind: Role
name: ${RoleName}
apiGroup: rbac.authorization.k8s.io
EOF
4.3. 创建kubeconfig
# 5. 基于secret获取token
TOKEN=$(kubectl get secret ${SecretName} -n ${ServiceAccountNS} -o jsonpath={".data.token"} | base64 -d)
# 6. 创建kubeconfig文件
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config set-cluster ${KubeConfigCluster} \
--server=https://${APISERVER} \
--certificate-authority=${CaFilePath} \
--embed-certs=true
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config set-credentials ${USER} --token=${TOKEN}
# 与cluster级别kubeconfig不同的增加了--namespace
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config set-context ${USER}@${KubeConfigCluster} \
--cluster=${KubeConfigCluster} --user=${USER} --namespace=${USER_NAMESPACE}
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config use-context ${USER}@${KubeConfigCluster}
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config view
5. 创建token来访问apiserver
以上方式是通过创建ServiceAccount的Secret的方式来获取token,该token是永久生效的,如果要使token失效可以删除对应的Secret对象,
如果要使用指定过期期限的token权限,可以通过以下命令生成:
# 生成过期时间365天的token
TOKEN=$(kubectl -n ${NAMESPACE} create token ${USER} --duration 8760h)
kubectl --kubeconfig=${KubeDir}/${USER}.yaml config set-credentials ${USER} --token=${TOKEN}
参考:
8.3 - 节点迁移
8.3.1 - 指定节点调度与隔离
1. NodeSelector
1.1. 概念
如果需要限制Pod到指定的Node上运行,则可以给Node打标签并给Pod配置NodeSelector。
1.2. 使用方式
1.2.1. 给Node打标签
# get node的name
kubectl get nodes
# 设置Label
kubectl label nodes <node-name> <label-key>=<label-value>
# 例如
kubectl label nodes node-1 disktype=ssd
# 查看Node的Label
kubectl get nodes --show-labels
# 删除Node的label
kubectl label node <node-name> <label-key>-
1.2.2. 给Pod设置NodeSelector
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd # 对应Node的Label
1.3. 亲和性(Affinity)和反亲和性(Anti-affinity)
待补充
2. Taint 和 Toleration
2.1. 概念
nodeSelector可以通过打标签的形式让Pod被调度到指定的Node上,Taint 则相反,它使节点能够排斥一类特定的Pod,除非Pod被指定了toleration的标签。(taint即污点,Node被打上污点;只有容忍[toleration]这些污点的Pod才可能被调度到该Node)。
2.2. 使用方式
2.2.1. kubectl taint
# 给节点增加一个taint,它的key是<key>,value是<value>,effect是NoSchedule。
kubectl taint nodes <node_name> <key>=<value>:NoSchedule
只有拥有和这个taint相匹配的toleration的pod才能够被分配到 node_name 这个节点。
例如,在 PodSpec 中定义 pod 的 toleration:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
tolerations:
- key: "key"
operator: "Exists"
effect: "NoSchedule"
2.2.2. 匹配规则:
一个 toleration 和一个 taint 相“匹配”是指它们有一样的 key 和 effect ,并且:
- 如果
operator是Exists(此时 toleration 不能指定value) - 如果
operator是Equal,则它们的value应该相等
特殊情况:
-
如果一个 toleration 的
key为空且 operator 为Exists,表示这个 toleration 与任意的 key 、 value 和 effect 都匹配,即这个 toleration 能容忍任意 taint。tolerations: - operator: "Exists" -
如果一个 toleration 的
effect为空,则key值与之相同的相匹配 taint 的effect可以是任意值。tolerations: - key: "key" operator: "Exists"
一个节点可以设置多个taint,一个pod也可以设置多个toleration。Kubernetes 处理多个 taint 和 toleration 的过程就像一个过滤器:从一个节点的所有 taint 开始遍历,过滤掉那些 pod 中存在与之相匹配的 toleration 的 taint。余下未被过滤的 taint 的 effect 值决定了 pod 是否会被分配到该节点,特别是以下情况:
- 如果未被过滤的 taint 中存在一个以上 effect 值为
NoSchedule的 taint,则 Kubernetes 不会将 pod 分配到该节点。 - 如果未被过滤的 taint 中不存在 effect 值为
NoSchedule的 taint,但是存在 effect 值为PreferNoSchedule的 taint,则 Kubernetes 会尝试将 pod 分配到该节点。 - 如果未被过滤的 taint 中存在一个以上 effect 值为
NoExecute的 taint,则 Kubernetes 不会将 pod 分配到该节点(如果 pod 还未在节点上运行),或者将 pod 从该节点驱逐(如果 pod 已经在节点上运行)。
2.2.3. effect的类型
-
NoSchedule:只有拥有和这个 taint 相匹配的 toleration 的 pod 才能够被分配到这个节点。 -
PreferNoSchedule:系统会尽量避免将 pod 调度到存在其不能容忍 taint 的节点上,但这不是强制的。 -
NoExecute:任何不能忍受这个 taint 的 pod 都会马上被驱逐,任何可以忍受这个 taint 的 pod 都不会被驱逐。Pod可指定属性tolerationSeconds的值,表示pod 还能继续在节点上运行的时间。tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoExecute" tolerationSeconds: 3600
2.3. 使用场景
2.3.1. 专用节点
kubectl taint nodes <nodename> dedicated=<groupName>:NoSchedule
先给Node添加taint,然后给Pod添加相对应的 toleration,则该Pod可调度到taint的Node,也可调度到其他节点。
如果想让Pod只调度某些节点且某些节点只接受对应的Pod,则需要在Node上添加Label(例如:dedicated=groupName),同时给Pod的nodeSelector添加对应的Label。
2.3.2. 特殊硬件节点
如果某些节点配置了特殊硬件(例如CPU),希望不使用这些特殊硬件的Pod不被调度该Node,以便保留必要资源。即可给Node设置taint和label,同时给Pod设置toleration和label来使得这些Node专门被指定Pod使用。
# kubectl taint
kubectl taint nodes nodename special=true:NoSchedule
# 或者
kubectl taint nodes nodename special=true:PreferNoSchedule
2.3.3. 基于taint驱逐
effect 值 NoExecute ,它会影响已经在节点上运行的 pod,即根据策略对Pod进行驱逐。
- 如果 pod 不能忍受effect 值为
NoExecute的 taint,那么 pod 将马上被驱逐 - 如果 pod 能够忍受effect 值为
NoExecute的 taint,但是在 toleration 定义中没有指定tolerationSeconds,则 pod 还会一直在这个节点上运行。 - 如果 pod 能够忍受effect 值为
NoExecute的 taint,而且指定了tolerationSeconds,则 pod 还能在这个节点上继续运行这个指定的时间长度。
参考:
8.3.2 - 安全迁移节点
1. 迁移Pod
1.1. 设置节点是否可调度
确定需要迁移和被迁移的节点,将不允许被迁移的节点设置为不可调度。
# 查看节点
kubectl get nodes
# 设置节点为不可调度
kubectl cordon <NodeName>
# 设置节点为可调度
kubectl uncordon <NodeName>
1.2. 执行kubectl drain命令
# 驱逐节点的所有pod
kubectl drain <NodeName> --force --ignore-daemonsets
# 驱逐指定节点的pod
kubectl drain <NodeName> --ignore-daemonsets --pod-selector=pod-template-hash=88964949c
示例:
$ kubectl drain bjzw-prek8sredis-99-40 --force --ignore-daemonsets
node "bjzw-prek8sredis-99-40" already cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: kube-proxy-bjzw-prek8sredis-99-40; Ignoring DaemonSet-managed pods: calicoopsmonitor-mfpqs, arachnia-agent-j56n8
pod "pre-test-pro2-r-0-redis-2-8-19-1" evicted
pod "pre-test-hwh1-r-8-redis-2-8-19-2" evicted
pod "pre-eos-hdfs-vector-eos-hdfs-redis-2-8-19-0" evicted
1.3. 特别说明
对于statefulset创建的Pod,kubectl drain的说明如下:
kubectl drain操作会将相应节点上的旧Pod删除,并在可调度节点上面起一个对应的Pod。当旧Pod没有被正常删除的情况下,新Pod不会起来。例如:旧Pod一直处于Terminating状态。
对应的解决方式是通过重启相应节点的kubelet,或者强制删除该Pod。
示例:
# 重启发生`Terminating`节点的kubelet
systemctl restart kubelet
# 强制删除`Terminating`状态的Pod
kubectl delete pod <PodName> --namespace=<Namespace> --force --grace-period=0
2. kubectl drain 流程图
3. TroubleShooting
1、存在不是通过ReplicationController, ReplicaSet, Job, DaemonSet 或者 StatefulSet创建的Pod(即静态pod,通过文件方式创建的),所以需要设置强制执行的参数--force。
$ kubectl drain bjzw-prek8sredis-99-40
node "bjzw-prek8sredis-99-40" already cordoned
error: unable to drain node "bjzw-prek8sredis-99-40", aborting command...
There are pending nodes to be drained:
bjzw-prek8sredis-99-40
error: DaemonSet-managed pods (use --ignore-daemonsets to ignore): calicoopsmonitor-mfpqs, arachnia-agent-j56n8; pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet (use --force to override): kube-proxy-bjzw-prek8sredis-99-40
2、存在DaemonSet方式管理的Pod,需要设置--ignore-daemonsets参数忽略报错。
$ kubectl drain bjzw-prek8sredis-99-40 --force
node "bjzw-prek8sredis-99-40" already cordoned
error: unable to drain node "bjzw-prek8sredis-99-40", aborting command...
There are pending nodes to be drained:
bjzw-prek8sredis-99-40
error: DaemonSet-managed pods (use --ignore-daemonsets to ignore): calicoopsmonitor-mfpqs, arachnia-agent-j56n8
4. kubectl drain
$ kubectl drain --help
Drain node in preparation for maintenance.
The given node will be marked unschedulable to prevent new pods from arriving. 'drain' evicts the pods if the API
server supports https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ . Otherwise, it will use normal DELETE
to delete the pods. The 'drain' evicts or deletes all pods except mirror pods (which cannot be deleted through the API
server). If there are daemon set-managed pods, drain will not proceed without --ignore-daemonsets, and regardless it
will not delete any daemon set-managed pods, because those pods would be immediately replaced by the daemon set
controller, which ignores unschedulable markings. If there are any pods that are neither mirror pods nor managed by a
replication controller, replica set, daemon set, stateful set, or job, then drain will not delete any pods unless you
use --force. --force will also allow deletion to proceed if the managing resource of one or more pods is missing.
'drain' waits for graceful termination. You should not operate on the machine until the command completes.
When you are ready to put the node back into service, use kubectl uncordon, which will make the node schedulable again.
https://kubernetes.io/images/docs/kubectl_drain.svg
Examples:
# Drain node "foo", even if there are pods not managed by a replication controller, replica set, job, daemon set or
stateful set on it
kubectl drain foo --force
# As above, but abort if there are pods not managed by a replication controller, replica set, job, daemon set or
stateful set, and use a grace period of 15 minutes
kubectl drain foo --grace-period=900
Options:
--chunk-size=500: Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and
may change in the future.
--delete-emptydir-data=false: Continue even if there are pods using emptyDir (local data that will be deleted when
the node is drained).
--disable-eviction=false: Force drain to use delete, even if eviction is supported. This will bypass checking
PodDisruptionBudgets, use with caution.
--dry-run='none': Must be "none", "server", or "client". If client strategy, only print the object that would be
sent, without sending it. If server strategy, submit server-side request without persisting the resource.
--force=false: Continue even if there are pods not managed by a ReplicationController, ReplicaSet, Job, DaemonSet
or StatefulSet.
--grace-period=-1: Period of time in seconds given to each pod to terminate gracefully. If negative, the default
value specified in the pod will be used.
--ignore-daemonsets=false: Ignore DaemonSet-managed pods.
--ignore-errors=false: Ignore errors occurred between drain nodes in group.
--pod-selector='': Label selector to filter pods on the node
-l, --selector='': Selector (label query) to filter on
--skip-wait-for-delete-timeout=0: If pod DeletionTimestamp older than N seconds, skip waiting for the pod.
Seconds must be greater than 0 to skip.
--timeout=0s: The length of time to wait before giving up, zero means infinite
Usage:
kubectl drain NODE [options]
Use "kubectl options" for a list of global command-line options (applies to all commands).
参考文档:
8.4 - 镜像仓库
8.4.1 - 配置私有镜像仓库
1. 镜像仓库的基本操作
1.1. 登录镜像仓库
docker login -u <username> -p <password> <registry-addr>
1.2. 拉取镜像
docker pull https://registry.xxx.com/dev/nginx:latest
1.3. 推送镜像
docker push https://registry.xxx.com/dev/nginx:latest
1.4. 重命名镜像
docker tag <old-image> <new-image>
2. docker.xxx.com镜像仓库
使用docker.xxx.com镜像仓库。
2.1. 所有节点配置insecure-registries
#cat /etc/docker/daemon.json
{
"data-root": "/data/docker",
"debug": false,
"insecure-registries": [
...
"docker.xxx.com:8080"
],
...
}
2.2. 所有节点配置/var/lib/kubelet/config.json
具体参考:configuring-nodes-to-authenticate-to-a-private-registry
- 在某个节点登录docker.xxx.com:8080镜像仓库,会更新 $HOME/.docker/config.json
- 检查$HOME/.docker/config.json是否有该镜像仓库的auth信息。
#cat ~/.docker/config.json
{
"auths": {
"docker.xxx.com:8080": {
"auth": "<此处为凭证信息>"
}
},
"HttpHeaders": {
"User-Agent": "Docker-Client/18.09.9 (linux)"
}
}
- 将
$HOME/.docker/config.json拷贝到所有的Node节点上的/var/lib/kubelet/config.json。
# 获取所有节点的IP
nodes=$(kubectl get nodes -o jsonpath='{range .items[*].status.addresses[?(@.type=="ExternalIP")]}{.address} {end}')
# 拷贝到所有节点
for n in $nodes; do scp ~/.docker/config.json root@$n:/var/lib/kubelet/config.json; done
2.3. 创建docker.xxx.com镜像的pod
指定镜像为:docker.xxx.com:8080/public/2048:latest
完整pod.yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
generation: 1
labels:
k8s-app: dockeroa-hub
qcloud-app: dockeroa-hub
name: dockeroa-hub
namespace: test
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: dockeroa-hub
qcloud-app: dockeroa-hub
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
k8s-app: dockeroa-hub
qcloud-app: dockeroa-hub
spec:
containers:
- image: docker.xxx.com:8080/public/2048:latest
imagePullPolicy: Always
name: game
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 250m
memory: 256Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
nodeName: 192.168.1.1
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
查看pod状态
#kgpoowide -n game
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
docker-oa-757bbbddb5-h6j7m 1/1 Running 0 14m 192.168.2.51 192.168.1.1 <none> <none>
docker-oa-757bbbddb5-jp5dw 1/1 Running 0 14m 192.168.1.32 192.168.1.2 <none> <none>
docker-oa-757bbbddb5-nlw9f 1/1 Running 0 14m 192.168.0.43 192.168.1.3 <none> <none>
参考:
8.4.2 - 拉取私有镜像
本文介绍通过pod指定 ImagePullSecrets来拉取私有镜像仓库的镜像
1. 创建secret
secret是namespace级别的,创建时候需要指定namespace。
kubectl create secret docker-registry <name> --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD -n <NAMESPACE>
2. 添加ImagePullSecrets到serviceAccount
可以通过将ImagePullSecrets到serviceAccount的方式来自动给pod添加imagePullSecrets参数值。
serviceAccount同样是namespace级别,只对该namespace生效。
#kubectl get secrets -n dev
NAME TYPE DATA AGE
docker.xxxx.com kubernetes.io/dockerconfigjson 1 6h23m
将ImagePullSecrets添加到serviceAccount对象中。
默认serviceAccount对象如下
#kubectl get serviceaccount default -n dev -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2020-02-27T03:30:38Z"
name: default
namespace: dev
resourceVersion: "11651567"
selfLink: /api/v1/namespaces/dev/serviceaccounts/default
uid: 85bcdd31-5911-11ea-9429-6c92bf3b7c33
secrets:
- name: default-token-s7wfn
编辑或修改serviceAccount内容,增加imagePullSecrets字段。
imagePullSecrets:
- name: docker.xxxx.com
kubectl edit serviceaccount default -n dev
修改后内容为:
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2020-02-27T03:30:38Z"
name: default
namespace: dev
resourceVersion: "11651567"
selfLink: /api/v1/namespaces/dev/serviceaccounts/default
uid: 85bcdd31-5911-11ea-9429-6c92bf3b7c33
secrets:
- name: default-token-s7wfn
imagePullSecrets:
- name: docker.xxxx.com
其中imagePullSecrets字段是一个数组,可以配置多个镜像仓库的账号密码。
例如:
apiVersion: v1
kind: ServiceAccount
...
imagePullSecrets:
- name: docker.xxxx.com
- name: docker.test.xxxx.com
3. 创建带有imagePullSecrets的pod
如果已经执行了第二步操作,添加ImagePullSecrets到serviceAccount,则无需在pod中指定imagePullSecrets参数,默认会自动添加。
如果没有添加ImagePullSecrets到serviceAccount,则在pod中指定imagePullSecrets参数引用创建的镜像仓库的secret。
spec:
imagePullSecrets:
- name: docker.xxxx.com
4. 说明
由于secret和serviceaccount对象是对namespace级别生效,因此不同的namespace需要再次创建和更新这两个对象。该场景适合不同用户具有独立的镜像仓库的密码,可以通过该方式创建不同的镜像密码使用的secret来拉取不同的镜像部署。
参考:
8.5 - 版本发布
8.5.1 - 金丝雀发布
Deployment配置金丝雀发布
金丝雀发布是指控制更新过程中的滚动节奏,通过“暂停”(pause)或“继续”(resume)更新发布操作。通过一小部分的版本发布实例来观察新版本是否有异常,如果没有异常则依次发布剩余的实例。
1. 设置发版节奏
主要是以下字段的设置:
- maxSurge:最大发版实例数,可以创建的超出期望 Pod 个数的 Pod 数量。可以是百分比或者是数字。
- maxUnavailable:最大不可用实例数。可以是百分比或数字。
- minReadySeconds :新建的 Pod 在没有任何容器崩溃的情况下就绪并被系统视为可用的最短秒数。 默认为 0(Pod 就绪后即被视为可用)。可将该值设置为5-10(秒),防止新起的pod发生crash,进而影响服务的可用性,保证集群在更新过程的稳定性。
# 例如将maxSurge设置为1,maxUnavailable设置为0
$ kubectl patch deployment myapp-deploy -p '{"spec": {"strategy": {"rollingUpdate": {"maxSurge": 1, "maxUnavailable": 0}}}}'
2. 升级版本并暂停
kubectl set image deployment myapp-deploy myapp=kubernetes/myapp:v3 && \
kubectl rollout pause deployments myapp-deploy
3. 查看升级状态
$ kubectl rollout status deployment myapp-deploy
Waiting for deployment "myapp-deploy" rollout to finish: 1 out of 3 new replicas have been updated...
4. 恢复继续发版
观察灰度的实例的流量是否正常,如果正常则继续发版,如果不正常则回滚之前的升级。
$ kubectl rollout resume deployments myapp-deploy
5. 回滚发布
5.1. 回滚上一个版本
kubectl rollout undo deployments myapp-deploy
5.2. 查看历史版本
$ kubectl rollout history deployment myapp-deploy
deployment.apps/myapp-deploy
REVISION CHANGE-CAUSE
3 <none>
5 <none>
6 <none>
5.3. 回滚指定版本
kubectl rollout undo deployment myapp-deploy --to-revision 3
参考:
- https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/
- https://kubernetes.io/zh-cn/docs/concepts/cluster-administration/manage-deployment/#canary-deployments
- https://kubernetes.renkeju.com/chapter_5/5.3.4.Canary_release.html
- https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/workload-resources/deployment-v1/#DeploymentSpec
8.5.2 - Kruise Rollout发布
1. kruise-rollout简介
金丝雀发布是逐步将流量导向新版本的应用,以最小化风险。具体过程包括:
- 部署新版本的 Pod。
- 将一部分流量分配到新版本(通常比例很小)。
- 逐步增加新版本的流量比例,直到完成全量发布。
Kruise Rollouts 是一个 Bypass(旁路) 组件,提供 高级渐进式交付功能 。可以通过Kruise Rollouts插件来实现金丝雀发布的能力。
| 组件 | Kruise Rollouts |
|---|---|
| 核心概念 | 增强现有的工作负载 |
| 架构 | Bypass |
| 插拔和热切换 | 是 |
| 发布类型 | 多批次、金丝雀、A/B测试、全链路灰度 |
| 工作负载类型 | Deployment、StatefulSet、CloneSet、Advanced StatefulSet、Advanced DaemonSet |
| 流量类型 | Ingress、GatewayAPI、CRD(需要 Lua 脚本) |
| 迁移成本 | 无需迁移工作负载和Pods |
| HPA 兼容性 | 是 |
2. 安装kruise-rollout
2.1. helm安装kruise-rollout
helm repo add openkruise https://openkruise.github.io/charts/
helm repo update
kubectl create ns openkruise
helm install kruise-rollout openkruise/kruise-rollout -n openkruise
# 升级到指定版本
helm upgrade kruise-rollout openkruise/kruise-rollout --version 0.5.0
查看部署结果
通过helm和kubectl的命令可以看到创建了几个crd和kruise-rollout-controller-manager的pod来提供rollout的功能。
# helm list -A
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
kruise-rollout openkruise 1 2024-11-24 16:52:36.067327844 +0800 +08 deployed kruise-rollout-0.5.0 0.5.0
# kubectl get po -n kruise-rollout
NAME READY STATUS RESTARTS AGE
kruise-rollout-controller-manager-875654888-rpxds 1/1 Running 0 87s
kruise-rollout-controller-manager-875654888-w75xj 1/1 Running 0 87s
# kubectl get crd |grep kruise
batchreleases.rollouts.kruise.io 2024-11-24T08:52:36Z
rollouthistories.rollouts.kruise.io 2024-11-24T08:52:36Z
rollouts.rollouts.kruise.io 2024-11-24T08:52:36Z
trafficroutings.rollouts.kruise.io 2024-11-24T08:52:36Z
2.2. 安装kubectl-kruise
kubectl-kruise用于执行rollout发版和回退等操作的二进制命令。
kubectl-kruise rollout approve:执行下一批版本发布kubectl-kruise rollout undo:回退全部发版批次
wget https://github.com/openkruise/kruise-tools/releases/download/v1.1.7/kubectl-kruise-linux-amd64-v1.1.7.tar.gz
tar -zvxf kubectl-kruise-linux-amd64-v1.1.7.tar.gz
mv linux-amd64/kubectl-kruise /usr/local/bin/
3. 使用指南
先部署一个k8s deployment对象,例如部署10个nginx:1.26的容器。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
namespace: default
spec:
replicas: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.26
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
3.1. 创建发布策略(rollout对象)
我们以使用多批次灰度发布为例。
workloadRef定义作用于哪个deployment对象。strategy定义发布策略。
以下是发布策略示例描述:
- 在第一批次:只升级 1个Pod;
- 在第二批次:升级 50% 的 Pods,即 5个已更新的Pod;
- 在第三批次:升级 100% 的 Pods,即 10个已更新的Pod。
$ kubectl apply -f - <<EOF
apiVersion: rollouts.kruise.io/v1beta1
kind: Rollout
metadata:
name: rollouts-nginx
namespace: default
spec:
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: nginx
strategy:
canary:
enableExtraWorkloadForCanary: false
steps:
- replicas: 1
- replicas: 50%
- replicas: 100%
EOF
3.2. 升级发布版本(发布第一批次)
升级nginx:1.26到nginx:1.27
kubectl set image deployment nginx nginx=nginx:1.27
查看灰度结果:
# kubectl get rs -L pod-template-hash -w
NAME DESIRED CURRENT READY AGE POD-TEMPLATE-HASH
nginx-68556bc579 1 1 1 8s 68556bc579 # nginx:1.27
nginx-d7f5d89c9 9 9 9 24m d7f5d89c9 # nginx:1.26
查看rollout状态:
rollout对象的状态主要描述了当前处于哪个rollout阶段及总状态和子阶段状态的信息。
# kubectl get rollout rollouts-nginx -oyaml
apiVersion: rollouts.kruise.io/v1beta1
kind: Rollout
metadata:
name: rollouts-nginx
namespace: default
spec:
disabled: false
strategy:
canary:
steps:
- pause: {}
replicas: 1
- pause: {}
replicas: 50%
- pause: {}
replicas: 100%
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: nginx
status:
canaryStatus:
canaryReadyReplicas: 1
canaryReplicas: 1
canaryRevision: 5c9556986d
currentStepIndex: 1
currentStepState: StepPaused # 当前步骤状态
lastUpdateTime: "2024-11-24T11:52:56Z"
message: BatchRelease is at state Ready, rollout-id , step 1
observedWorkloadGeneration: 36
podTemplateHash: d7f5d89c9
rolloutHash: 77cxd69w47b7bwddwv2w7vxvb4xxdbwcx9x289vw69w788w4w6z4x8dd4vbz2zbw
stableRevision: 68556bc579
conditions:
- lastTransitionTime: "2024-11-24T11:52:47Z"
lastUpdateTime: "2024-11-24T11:52:47Z"
message: Rollout is in Progressing
reason: InRolling
status: "True"
type: Progressing
message: Rollout is in step(1/3), and you need manually confirm to enter the next
step # rollout信息
observedGeneration: 2
phase: Progressing # 整体状态
3.3. 发布第二批次
kubectl-kruise rollout approve rollout/rollouts-nginx -n default
查看灰度结果:
# kubectl get rs -L pod-template-hash -w
NAME DESIRED CURRENT READY AGE POD-TEMPLATE-HASH
nginx-68556bc579 5 5 5 7m24s 68556bc579 # nginx:1.27
nginx-d7f5d89c9 5 5 5 32m d7f5d89c9 # nginx:1.26
# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-68556bc579-24lpl 1/1 Running 0 7m34s # nginx:1.27 第一批次
nginx-68556bc579-2dph8 1/1 Running 0 14s # nginx:1.27 第二批次
nginx-68556bc579-57pqt 1/1 Running 0 14s # nginx:1.27 第二批次
nginx-68556bc579-879s9 1/1 Running 0 14s # nginx:1.27 第二批次
nginx-68556bc579-fwt52 1/1 Running 0 14s # nginx:1.27 第二批次
nginx-d7f5d89c9-5fbfp 1/1 Running 0 30m # 其余为第三批次
nginx-d7f5d89c9-gkz9p 1/1 Running 0 30m
nginx-d7f5d89c9-jhxwl 1/1 Running 0 30m
nginx-d7f5d89c9-vrqfz 1/1 Running 0 30m
nginx-d7f5d89c9-zk2sj 1/1 Running 0 30m
3.4. 发布第三批次
kubectl-kruise rollout approve rollout/rollouts-nginx -n default
查看结果:
# kubectl get rs -L pod-template-hash -w
NAME DESIRED CURRENT READY AGE POD-TEMPLATE-HASH
nginx-68556bc579 10 10 10 12m 68556bc579 # nginx:1.27
nginx-d7f5d89c9 0 0 0 37m d7f5d89c9
3.5. 发布回滚
该回滚会把全部批次的pod都回滚到第一批发版前的版本。
kubectl-kruise rollout undo rollout/rollouts-nginx -n default
4. k8s对象变更
kruise-rollout的原理主要还是劫持Deployment和ReplicaSet的操作,例如在升级deployment的时候会把deployment原先的strategy策略放在annotations中暂存,再增加paused=true的参数暂停deployment的发布。在rollout全部结束后再恢复原先的deployment参数,主要包括paused和strategy。
以下是rollout中间阶段的deployment信息:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
batchrelease.rollouts.kruise.io/control-info: '{"apiVersion":"rollouts.kruise.io/v1beta1","kind":"BatchRelease","name":"rollouts-nginx","uid":"8f29624b-ab77-49d1-abbe-aa92bb3998e2","controller":true,"blockOwnerDeletion":true}'
deployment.kubernetes.io/revision: "5"
rollouts.kruise.io/deployment-extra-status: '{"updatedReadyReplicas":1,"expectedUpdatedReplicas":1}'
rollouts.kruise.io/deployment-strategy: '{"rollingStyle":"Partition","rollingUpdate":{"maxUnavailable":"25%","maxSurge":"25%"},"partition":1}' # deployment原本的strategy策略
rollouts.kruise.io/in-progressing: '{"rolloutName":"rollouts-nginx"}'
generation: 36
labels:
app: nginx
rollouts.kruise.io/controlled-by-advanced-deployment-controller: "true"
rollouts.kruise.io/stable-revision: 68556bc579
rollouts.kruise.io/workload-type: deployment
name: nginx
namespace: default
spec:
paused: true # 增加了paused参数
progressDeadlineSeconds: 600
replicas: 10
revisionHistoryLimit: 10
5. Rollout流量灰度
5.1. 添加pod 版本标签
为了适配Apisix或Nginx等k8s流量网关的灰度逻辑,我们通过k8s service的方式来动态获取Pod的IP(即endpoint),其中包括全量pod,灰度pod和非灰度pod。因此我们在每次发版的deployment给pod加上所属的版本标签。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
namespace: default
spec:
replicas: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: nginx
version: "1.26" # 增加pod对应的版本标签,用于service联动
spec:
containers:
- image: nginx:1.26
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
5.2. 添加灰度pod的service
创建三个service来对应全量pod,灰度pod和非灰度pod。
全量pod的service
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
selector:
app: nginx # 全量pod的标签
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
灰度pod的service
apiVersion: v1
kind: Service
metadata:
name: nginx-canary
namespace: default
labels:
version: canary
spec:
selector:
app: nginx
version: "1.27" # 增加灰度版本的标签,对应灰度的pod
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
非灰度pod的service
apiVersion: v1
kind: Service
metadata:
name: nginx-stable
namespace: default
spec:
selector:
app: nginx
version: "1.26" # 增加非灰度版本的标签,对应非灰度的pod
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
5.3. 动态获取灰度的endpoint
通过以上service的配置,可以在灰度的过程中动态获取灰度pod的endpoint,然后动态集成到流量网关中。
全量pod的service:在流量灰度前和流量灰度完成后可以把流量调度到这个service下的pod。灰度pod的service:可以设置把灰度流量调度到canary的service的pod。非灰度pod的service:可以把非灰度流量调度到stable的service的pod。
以下是灰度前后endpoint的变化:
# kubectl get endpoints
# 灰度前:canary的pod为0
nginx 10.0.1.139:80,10.0.1.202:80,10.0.1.203:80 + 7 more... 2d
nginx-canary <none> 2d
nginx-stable 10.0.1.139:80,10.0.1.202:80,10.0.1.203:80 + 7 more... 47h
# 灰度中:canary的pod增加
nginx 10.0.1.202:80,10.0.1.203:80,10.0.1.204:80 + 7 more... 2d <none>
nginx-canary 10.0.1.5:80 2d version=canary
nginx-stable 10.0.1.202:80,10.0.1.203:80,10.0.1.204:80 + 6 more... 47h version=stable
# 灰度完成:stable的pod为0
NAME ENDPOINTS AGE
nginx 10.0.1.139:80,10.0.1.202:80,10.0.1.203:80 + 7 more... 2d
nginx-canary 10.0.1.139:80,10.0.1.202:80,10.0.1.203:80 + 7 more... 2d
nginx-stable <none> 47h
6. 总结
k8s的deployment默认支持金丝雀发布,可以通过kubectl rollout的命令实现,通过配置deployment中的maxSurge和maxUnavailable来控制发版的节奏,可以通过以下命令来控制发版批次:
kubectl rollout pause:暂停发版kubectl rollout resume:继续发版kubectl rollout undo:回滚版本
但相对来讲,控制粒度没有那么精确,而OpenKruise的Rollouts插件提供了一种更加轻便,可控的发版工具,个人认为最大的优势是配置简单,且无需迁移工作负载和Pods,可以更好的集成到现有的容器平台中。因此可以通过该工具来实现金丝雀发版(多批次发版),实现灰度的能力。
参考:
8.5.3 - HPA[自动扩缩容]配置
1. HPA简介
HPA全称HorizontalPodAutoscaler,即pod水平扩容,增加或减少pod的数量。相对于VPA而言,VPA是增加或减少单个pod的CPU或内存。HPA主要作用于Deployment或Statefulset的工作负载,无法作用于Daemonset的工作负载。
示例图:

Kubernetes 将水平 Pod 自动扩缩实现为一个间歇运行的控制回路(它不是一个连续的过程)。间隔由 kube-controller-manager 的 --horizontal-pod-autoscaler-sync-period 参数设置(默认间隔为 15 秒)。
在每个时间段内,控制器管理器都会根据每个 HorizontalPodAutoscaler 定义中指定的指标查询资源利用率。 控制器管理器找到由 scaleTargetRef 定义的目标资源,然后根据目标资源的 .spec.selector 标签选择 Pod, 并从资源指标 API(针对每个 Pod 的资源指标)或自定义指标获取指标 API(适用于所有其他指标)。
HPA依赖metrics-server来获取CPU和内存数据,以下说明metrics-server的部署流程。
2. 部署metrics-server
下载metrics-server文件
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
修改启动参数,增加--kubelet-insecure-tls,否则会报获取接口证书失败。
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --metric-resolution=15s
- --kubelet-insecure-tls # 增加该参数
创建yaml服务
kubectl apply -f components.yaml
通过kubectl top查看资源信息
# kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
node1 144m 0% 5762Mi 2%
node2 337m 0% 5475Mi 2%
node3 100m 0% 5326Mi 2%
node4 302m 0% 5649Mi 2%
# kubectl top pod -n prometheus
NAME CPU(cores) MEMORY(bytes)
alertmanager-kube-prometheus-stack-alertmanager-0 1m 30Mi
kube-prometheus-stack-grafana-7688b45b4c-mvwd6 1m 225Mi
kube-prometheus-stack-kube-state-metrics-5d6578867c-25xbq 1m 21Mi
kube-prometheus-stack-operator-9c5fbdc68-nrn7h 1m 33Mi
kube-prometheus-stack-prometheus-node-exporter-8ghd8 1m 4Mi
kube-prometheus-stack-prometheus-node-exporter-brtp9 1m 4Mi
kube-prometheus-stack-prometheus-node-exporter-n4kdp 1m 4Mi
kube-prometheus-stack-prometheus-node-exporter-ttksv 1m 4Mi
prometheus-kube-prometheus-stack-prometheus-0 8m 622Mi
同时在k8s dashboard上也可以查看到实时的CPU和内存信息。

3. HPA配置
todo
参考:
- https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/
- https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/
- https://kubernetes.io/zh-cn/docs/tasks/debug/debug-cluster/resource-metrics-pipeline/#metrics-server
- https://github.com/kubernetes-sigs/metrics-server
- https://www.qikqiak.com/post/k8s-hpa-usage/
8.6 - helm工具
8.6.1 - helm的使用
1. 安装helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
2. 基本概念
Helm是用来管理k8s集群上的软件包。
-
Chart:代表helm软件包 -
Repository:软件包的存放仓库 -
Release:运行在k8s上的一个发布实例。
3. helm命令
Usage:
helm [command]
Available Commands:
completion generate autocompletion scripts for the specified shell
create create a new chart with the given name
dependency manage a chart's dependencies
env helm client environment information
get download extended information of a named release
help Help about any command
history fetch release history
install install a chart
lint examine a chart for possible issues
list list releases
package package a chart directory into a chart archive
plugin install, list, or uninstall Helm plugins
pull download a chart from a repository and (optionally) unpack it in local directory
push push a chart to remote
registry login to or logout from a registry
repo add, list, remove, update, and index chart repositories
rollback roll back a release to a previous revision
search search for a keyword in charts
show show information of a chart
status display the status of the named release
template locally render templates
test run tests for a release
uninstall uninstall a release
upgrade upgrade a release
verify verify that a chart at the given path has been signed and is valid
version print the client version information
4. 常用命令
4.1. helm search
- helm search hub:从 Artifact Hub 中查找并列出 helm charts。支持模糊匹配。
helm search hub wordpress
- helm search repo:基于指定仓库进行搜索。
helm repo add brigade https://brigadecore.github.io/charts
helm search repo brigade
# 列出所有版本
helm search repo apisix -l
4.2. helm install/uninstall
helm install <release_name> <chart_name>
# 示例
helm install happy-panda bitnami/wordpress -
# uninstall
helm uninstall RELEASE_NAME
安装自定义chart
helm install -f values.yaml bitnami/wordpress --generate-name
# 本地 chart 压缩包
helm install foo foo-0.1.1.tgz
# 解压后的 chart 目录
helm install foo path/to/foo
# 完整的 URL
helm install foo https://example.com/charts/foo-1.2.3.tgz
4.3. helm upgrade
helm upgrade happy-panda bitnami/wordpress
4.4. helm rollback
helm rollback <RELEASE> [REVISION] [flags]
4.5. helm repo
helm repo add dev https://example.com/dev-charts
helm repo list
helm repo remove
4.6. helm pull
从仓库下载并(可选)在本地目录解压。
helm pull [chart URL | repo/chartname]
helm pull [chart URL | repo/chartname] --version
5. 创建chart
5.1. 初始化chart
helm create mychart
查看生成的文件目录:
mychart
|-- charts # 目录用于存放所依赖的子chart
|-- Chart.yaml # 描述这个 Chart 的相关信息、包括名字、描述信息、版本等
|-- templates
| |-- deployment.yaml
| |-- _helpers.tpl # 模板助手文件,定义的值可在模板中使用
| |-- hpa.yaml
| |-- ingress.yaml
| |-- NOTES.txt # Chart 部署到集群后的一些信息,例如:如何使用、列出缺省值
| |-- serviceaccount.yaml
| |-- service.yaml
| `-- tests
| `-- test-connection.yaml
`-- values.yaml # 模板的值文件,这些值会在安装时应用到 GO 模板生成部署文件
移除默认模板文件,并添加自己的模板文件。
rm -rf mysubchart/templates/*
5.2. 调试模板
helm lint是验证chart是否遵循最佳实践的首选工具。helm template --debug在本地测试渲染chart模板。helm install --dry-run --debug:我们已经看到过这个技巧了,这是让服务器渲染模板的好方法,然后返回生成的清单文件。helm get manifest: 这是查看安装在服务器上的模板的好方法。
参考:
8.6.2 - kine的使用
创建kine的数据表
如果kine连接MySQL使用的用户有创建表的权限,则会自动创建表名为kine的数据表,如果MySQL用户没有创建表的权限,则需要手动创建数据表,建表语句如下:
CREATE TABLE `kine` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(630) CHARACTER SET ascii DEFAULT NULL,
`created` int(11) DEFAULT NULL,
`deleted` int(11) DEFAULT NULL,
`create_revision` bigint(20) unsigned DEFAULT NULL,
`prev_revision` bigint(20) unsigned DEFAULT NULL,
`lease` int(11) DEFAULT NULL,
`value` mediumblob,
`old_value` mediumblob,
PRIMARY KEY (`id`),
UNIQUE KEY `kine_name_prev_revision_uindex` (`name`,`prev_revision`),
KEY `kine_name_index` (`name`),
KEY `kine_name_id_index` (`name`,`id`),
KEY `kine_id_deleted_index` (`id`,`deleted`),
KEY `kine_prev_revision_index` (`prev_revision`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
FAQ
问题1:converting NULL to int64 is unsupported
报错详情:
F0124 18:01:40.316896 44 controller.go:161] Unable to perform initial IP allocation check: unable to refresh the service IP block: rpc error: code = Unknown desc = sql: Scan error on column index 0, name "prev_revision": converting NULL to int64 is unsupported
原因:
mysql没有开启auto commit
SHOW VARIABLES LIKE 'autocommit';
解决方案:
开启mysql的autocommit配置。
8.7 - 访问控制
8.7.1 - 使用 RBAC 鉴权
本文基于https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/rbac/ 整理。
1. RBAC介绍
基于角色的访问控制【Role-based access control (RBAC)】是一种基于组织中用户的角色来调节控制对 计算机或网络资源的访问的方法。
RBAC 鉴权机制使用 rbac.authorization.k8s.io API 组来驱动鉴权决定, 允许你通过 Kubernetes API 动态配置策略。
要启用 RBAC,在启动 API 服务器时将 --authorization-mode 参数设置为一个逗号分隔的列表并确保其中包含 RBAC。
kube-apiserver --authorization-mode=Example,RBAC --<其他选项> --<其他选项>
编写自定义CRD控制器或部署其他开源组件时,经常需要给组件配置RBAC权限。
理解RBAC权限体系,只需要理解以下三个概念对象即可:
-
【权限】Role:角色,它其实是一组规则,定义了一组对 Kubernetes API 对象的操作权限。
-
【用户】Subject:被作用者,既可以是“人”,也可以是“机器”,也可以是你在 Kubernetes 里定义的“用户”。
-
【授权】RoleBinding:定义了“被作用者”和“角色”的绑定关系。
一句话理解RBAC,就是将定义的权限与定义的用户之间的关系进行绑定,即授权某个用户某些权限。
快速授权脚本可以参考:https://github.com/huweihuang/kubeadm-scripts/tree/main/kubeconfig/token
2. API对象
角色(权限)---角色(权限)绑定---用户(subject)
| 集群级别范围 | 命名空间范围 | |
|---|---|---|
| 权限 | ClusterRole | Role |
| 授权 | ClusterRoleBinding | RoleBinding |
| 用户 | ServiceAccout |
以下从权限、用户、授权三个概念进行说明。完成一套授权逻辑,主要分为三个步骤
-
创建权限,即创建Role或ClusterRole对象。
-
创建用户,即创建ServiceAccount对象。
-
分配权限,即创建RoleBinding或ClusterRoleBinding对象。
3. 权限
RBAC 的 Role 或 ClusterRole 中包含一组代表相关权限的规则。 这些权限是纯粹累加的(不存在拒绝某操作的规则)。
3.1. 命名空间权限[Role]
Role是针对指定namespace的权限,即创建的时候需要指定namespace。
示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 标明 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]
3.2. 集群级别权限[ClusterRole]
ClusterRole用于指定集群内的资源:
-
集群范围资源(比如节点(Node))
-
非资源端点(比如
/healthz) -
跨名字空间访问的名字空间作用域的资源(如 访问所有namespace下的Pod)
示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" 被忽略,因为 ClusterRoles 不受名字空间限制
name: secret-reader
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 Secret 资源的名称为 "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
3.3. 默认权限角色
在 Kubernetes 中已经内置了很多个为系统保留的 ClusterRole,它们的名字都以system:开头。可以通过 kubectl get clusterroles 查看到它们。
超级用户(Super-User)角色(cluster-admin)、 使用 ClusterRoleBinding 在集群范围内完成授权的角色(cluster-status)、 以及使用 RoleBinding 在特定名字空间中授予的角色(admin、edit、view)。
| 默认 ClusterRole | 默认 ClusterRoleBinding | 描述 |
|---|---|---|
| cluster-admin | system:masters 组 | 允许超级用户在平台上的任何资源上执行所有操作。 当在 ClusterRoleBinding 中使用时,可以授权对集群中以及所有名字空间中的全部资源进行完全控制。 当在 RoleBinding 中使用时,可以授权控制角色绑定所在名字空间中的所有资源,包括名字空间本身。 |
| admin | 无 | 允许管理员访问权限,旨在使用 RoleBinding 在名字空间内执行授权。如果在 RoleBinding 中使用,则可授予对名字空间中的大多数资源的读/写权限, 包括创建角色和角色绑定的能力。 此角色不允许对资源配额或者名字空间本身进行写操作。 此角色也不允许对 Kubernetes v1.22+ 创建的 Endpoints 进行写操作。 更多信息参阅 “Endpoints 写权限”小节。 |
| edit | 无 | 允许对名字空间的大多数对象进行读/写操作。此角色不允许查看或者修改角色或者角色绑定。 不过,此角色可以访问 Secret,以名字空间中任何 ServiceAccount 的身份运行 Pod, 所以可以用来了解名字空间内所有服务账户的 API 访问级别。 此角色也不允许对 Kubernetes v1.22+ 创建的 Endpoints 进行写操作。 更多信息参阅 “Endpoints 写操作”小节。 |
| view | 无 | 允许对名字空间的大多数对象有只读权限。 它不允许查看角色或角色绑定。此角色不允许查看 Secrets,因为读取 Secret 的内容意味着可以访问名字空间中 ServiceAccount 的凭据信息,进而允许利用名字空间中任何 ServiceAccount 的身份访问 API(这是一种特权提升)。 |
4. 用户
4.1. ServiceAccount
创建指定namespace的ServiceAccount对象。ServiceAccount可以在pod中被使用。
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: <Namespace>
name: <ServiceAccountName>
4.2. Secret
创建secret,绑定serviceaccount,会自动生成token。
k8s 1.24后的版本不再自动生成secret,绑定后当删除ServiceAccount时会自动删除secret
apiVersion: v1
kind: Secret
metadata:
name: <SecretName>
namespace: <Namespace>
annotations:
kubernetes.io/service-account.name: "ServiceAccountName"
type: kubernetes.io/service-account-token
5. 授权
5.1. 授权命名空间权限[RoleBinding]
RoleBinding角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。 它包含若干 主体(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。 RoleBinding 在指定的名字空间中执行授权,而 ClusterRoleBinding 在集群范围执行授权。
字段说明:
-
subjects:表示权限所授予的用户,包括ServiceAccount,Group,User。 -
roleRef:表示权限对应的角色,包括Role,ClusterRole。
示例:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ${USER}-rolebinding
namespace: ${NAMESPACE}
subjects:
- kind: ServiceAccount
name: ${ServiceAccountName}
namespace: ${ServiceAccountNS}
roleRef:
kind: ClusterRole
name: ${ROLE}
apiGroup: rbac.authorization.k8s.io
可以给指定命名空间下的serviceaccount授权其他命名空间的权限。只需要新增RoleBinding在预授权的命名空间下即可。可以理解为可以给一个用户分配多个命名空间的权限。
#!/bin/bash
set -e
# 给已存在的用户USER 添加其他NAMESPACE的权限
USER=$1
NAMESPACE=$2
ROLE=$3
ROLE=${ROLE:-edit}
ServiceAccountName="${USER}-user"
ServiceAccountNS="kubernetes-dashboard"
cat<<EOF | kubectl apply -f -
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ${USER}-rolebinding
namespace: ${NAMESPACE}
subjects:
- kind: ServiceAccount
name: ${ServiceAccountName}
namespace: ${ServiceAccountNS}
roleRef:
kind: ClusterRole
name: ${ROLE}
apiGroup: rbac.authorization.k8s.io
EOF
5.2. 授权集群级别权限[ClusterRoleBinding]
要跨整个集群完成访问权限的授予,可以使用一个 ClusterRoleBinding。
示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ${USER}
subjects:
- kind: ServiceAccount
name: ${USER}
namespace: ${NAMESPACE}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ${ROLE}
参考:
9 - 开发指南
9.1 - client-go的使用及源码分析
1. client-go简介
1.1 client-go说明
client-go是一个调用kubernetes集群资源对象API的客户端,即通过client-go实现对kubernetes集群中资源对象(包括deployment、service、ingress、replicaSet、pod、namespace、node等)的增删改查等操作。大部分对kubernetes进行前置API封装的二次开发都通过client-go这个第三方包来实现。
client-go官方文档:https://github.com/kubernetes/client-go
1.2 示例代码
git clone https://github.com/huweihuang/client-go.git
cd client-go
#保证本地HOME目录有配置kubernetes集群的配置文件
go run client-go.go
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
var kubeconfig *string
if home := homeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
// uses the current context in kubeconfig
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err.Error())
}
// creates the clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
for {
pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
if err != nil {
panic(err.Error())
}
fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))
time.Sleep(10 * time.Second)
}
}
func homeDir() string {
if h := os.Getenv("HOME"); h != "" {
return h
}
return os.Getenv("USERPROFILE") // windows
}
1.3 运行结果
➜ go run client-go.go
There are 9 pods in the cluster
There are 7 pods in the cluster
There are 7 pods in the cluster
There are 7 pods in the cluster
There are 7 pods in the cluster
2. client-go源码分析
client-go源码:https://github.com/kubernetes/client-go
client-go源码目录结构
- The
kubernetespackage contains the clientset to access Kubernetes API. - The
discoverypackage is used to discover APIs supported by a Kubernetes API server. - The
dynamicpackage contains a dynamic client that can perform generic operations on arbitrary Kubernetes API objects. - The
transportpackage is used to set up auth and start a connection. - The
tools/cachepackage is useful for writing controllers.
2.1 kubeconfig
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
获取kubernetes配置文件kubeconfig的绝对路径。一般路径为$HOME/.kube/config。该文件主要用来配置本地连接的kubernetes集群。
config内容如下:
apiVersion: v1
clusters:
- cluster:
server: http://<kube-master-ip>:8080
name: k8s
contexts:
- context:
cluster: k8s
namespace: default
user: ""
name: default
current-context: default
kind: Config
preferences: {}
users: []
2.2 rest.config
通过参数(master的url或者kubeconfig路径)和BuildConfigFromFlags方法来获取rest.Config对象,一般是通过参数kubeconfig的路径。
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
BuildConfigFromFlags函数源码
k8s.io/client-go/tools/clientcmd/client_config.go
// BuildConfigFromFlags is a helper function that builds configs from a master
// url or a kubeconfig filepath. These are passed in as command line flags for cluster
// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
// to the default config.
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
if kubeconfigPath == "" && masterUrl == "" {
glog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
kubeconfig, err := restclient.InClusterConfig()
if err == nil {
return kubeconfig, nil
}
glog.Warning("error creating inClusterConfig, falling back to default config: ", err)
}
return NewNonInteractiveDeferredLoadingClientConfig(
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}
2.3 clientset
通过*rest.Config参数和NewForConfig方法来获取clientset对象,clientset是多个client的集合,每个client可能包含不同版本的方法调用。
clientset, err := kubernetes.NewForConfig(config)
2.3.1 NewForConfig
NewForConfig函数就是初始化clientset中的每个client。
k8s.io/client-go/kubernetes/clientset.go
// NewForConfig creates a new Clientset for the given config.
func NewForConfig(c *rest.Config) (*Clientset, error) {
configShallowCopy := *c
...
var cs Clientset
cs.appsV1beta1, err = appsv1beta1.NewForConfig(&configShallowCopy)
...
cs.coreV1, err = corev1.NewForConfig(&configShallowCopy)
...
}
2.3.2 clientset的结构体
k8s.io/client-go/kubernetes/clientset.go
// Clientset contains the clients for groups. Each group has exactly one
// version included in a Clientset.
type Clientset struct {
*discovery.DiscoveryClient
admissionregistrationV1alpha1 *admissionregistrationv1alpha1.AdmissionregistrationV1alpha1Client
appsV1beta1 *appsv1beta1.AppsV1beta1Client
appsV1beta2 *appsv1beta2.AppsV1beta2Client
authenticationV1 *authenticationv1.AuthenticationV1Client
authenticationV1beta1 *authenticationv1beta1.AuthenticationV1beta1Client
authorizationV1 *authorizationv1.AuthorizationV1Client
authorizationV1beta1 *authorizationv1beta1.AuthorizationV1beta1Client
autoscalingV1 *autoscalingv1.AutoscalingV1Client
autoscalingV2beta1 *autoscalingv2beta1.AutoscalingV2beta1Client
batchV1 *batchv1.BatchV1Client
batchV1beta1 *batchv1beta1.BatchV1beta1Client
batchV2alpha1 *batchv2alpha1.BatchV2alpha1Client
certificatesV1beta1 *certificatesv1beta1.CertificatesV1beta1Client
coreV1 *corev1.CoreV1Client
extensionsV1beta1 *extensionsv1beta1.ExtensionsV1beta1Client
networkingV1 *networkingv1.NetworkingV1Client
policyV1beta1 *policyv1beta1.PolicyV1beta1Client
rbacV1 *rbacv1.RbacV1Client
rbacV1beta1 *rbacv1beta1.RbacV1beta1Client
rbacV1alpha1 *rbacv1alpha1.RbacV1alpha1Client
schedulingV1alpha1 *schedulingv1alpha1.SchedulingV1alpha1Client
settingsV1alpha1 *settingsv1alpha1.SettingsV1alpha1Client
storageV1beta1 *storagev1beta1.StorageV1beta1Client
storageV1 *storagev1.StorageV1Client
}
2.3.3 clientset.Interface
clientset实现了以下的Interface,因此可以通过调用以下方法获得具体的client。例如:
pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
clientset的方法集接口
k8s.io/client-go/kubernetes/clientset.go
type Interface interface {
Discovery() discovery.DiscoveryInterface
AdmissionregistrationV1alpha1() admissionregistrationv1alpha1.AdmissionregistrationV1alpha1Interface
// Deprecated: please explicitly pick a version if possible.
Admissionregistration() admissionregistrationv1alpha1.AdmissionregistrationV1alpha1Interface
AppsV1beta1() appsv1beta1.AppsV1beta1Interface
AppsV1beta2() appsv1beta2.AppsV1beta2Interface
// Deprecated: please explicitly pick a version if possible.
Apps() appsv1beta2.AppsV1beta2Interface
AuthenticationV1() authenticationv1.AuthenticationV1Interface
// Deprecated: please explicitly pick a version if possible.
Authentication() authenticationv1.AuthenticationV1Interface
AuthenticationV1beta1() authenticationv1beta1.AuthenticationV1beta1Interface
AuthorizationV1() authorizationv1.AuthorizationV1Interface
// Deprecated: please explicitly pick a version if possible.
Authorization() authorizationv1.AuthorizationV1Interface
AuthorizationV1beta1() authorizationv1beta1.AuthorizationV1beta1Interface
AutoscalingV1() autoscalingv1.AutoscalingV1Interface
// Deprecated: please explicitly pick a version if possible.
Autoscaling() autoscalingv1.AutoscalingV1Interface
AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface
BatchV1() batchv1.BatchV1Interface
// Deprecated: please explicitly pick a version if possible.
Batch() batchv1.BatchV1Interface
BatchV1beta1() batchv1beta1.BatchV1beta1Interface
BatchV2alpha1() batchv2alpha1.BatchV2alpha1Interface
CertificatesV1beta1() certificatesv1beta1.CertificatesV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Certificates() certificatesv1beta1.CertificatesV1beta1Interface
CoreV1() corev1.CoreV1Interface
// Deprecated: please explicitly pick a version if possible.
Core() corev1.CoreV1Interface
ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Extensions() extensionsv1beta1.ExtensionsV1beta1Interface
NetworkingV1() networkingv1.NetworkingV1Interface
// Deprecated: please explicitly pick a version if possible.
Networking() networkingv1.NetworkingV1Interface
PolicyV1beta1() policyv1beta1.PolicyV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Policy() policyv1beta1.PolicyV1beta1Interface
RbacV1() rbacv1.RbacV1Interface
// Deprecated: please explicitly pick a version if possible.
Rbac() rbacv1.RbacV1Interface
RbacV1beta1() rbacv1beta1.RbacV1beta1Interface
RbacV1alpha1() rbacv1alpha1.RbacV1alpha1Interface
SchedulingV1alpha1() schedulingv1alpha1.SchedulingV1alpha1Interface
// Deprecated: please explicitly pick a version if possible.
Scheduling() schedulingv1alpha1.SchedulingV1alpha1Interface
SettingsV1alpha1() settingsv1alpha1.SettingsV1alpha1Interface
// Deprecated: please explicitly pick a version if possible.
Settings() settingsv1alpha1.SettingsV1alpha1Interface
StorageV1beta1() storagev1beta1.StorageV1beta1Interface
StorageV1() storagev1.StorageV1Interface
// Deprecated: please explicitly pick a version if possible.
Storage() storagev1.StorageV1Interface
}
2.4 CoreV1Client
我们以clientset中的CoreV1Client为例做分析。
通过传入的配置信息rest.Config初始化CoreV1Client对象。
k8s.io/client-go/kubernetes/clientset.go
cs.coreV1, err = corev1.NewForConfig(&configShallowCopy)
2.4.1 corev1.NewForConfig
k8s.io/client-go/kubernetes/typed/core/v1/core_client.go
// NewForConfig creates a new CoreV1Client for the given config.
func NewForConfig(c *rest.Config) (*CoreV1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &CoreV1Client{client}, nil
}
corev1.NewForConfig方法本质是调用了rest.RESTClientFor(&config)方法创建RESTClient对象,即CoreV1Client的本质就是一个RESTClient对象。
2.4.2 CoreV1Client结构体
以下是CoreV1Client结构体的定义:
k8s.io/client-go/kubernetes/typed/core/v1/core_client.go
// CoreV1Client is used to interact with features provided by the group.
type CoreV1Client struct {
restClient rest.Interface
}
CoreV1Client实现了CoreV1Interface的接口,即以下方法,从而对kubernetes的资源对象进行增删改查的操作。
k8s.io/client-go/kubernetes/typed/core/v1/core_client.go
//CoreV1Client的方法
func (c *CoreV1Client) ComponentStatuses() ComponentStatusInterface {...}
//ConfigMaps
func (c *CoreV1Client) ConfigMaps(namespace string) ConfigMapInterface {...}
//Endpoints
func (c *CoreV1Client) Endpoints(namespace string) EndpointsInterface {...}
func (c *CoreV1Client) Events(namespace string) EventInterface {...}
func (c *CoreV1Client) LimitRanges(namespace string) LimitRangeInterface {...}
//Namespaces
func (c *CoreV1Client) Namespaces() NamespaceInterface {...}
//Nodes
func (c *CoreV1Client) Nodes() NodeInterface {...}
func (c *CoreV1Client) PersistentVolumes() PersistentVolumeInterface {...}
func (c *CoreV1Client) PersistentVolumeClaims(namespace string) PersistentVolumeClaimInterface {...}
//Pods
func (c *CoreV1Client) Pods(namespace string) PodInterface {...}
func (c *CoreV1Client) PodTemplates(namespace string) PodTemplateInterface {...}
//ReplicationControllers
func (c *CoreV1Client) ReplicationControllers(namespace string) ReplicationControllerInterface {...}
func (c *CoreV1Client) ResourceQuotas(namespace string) ResourceQuotaInterface {...}
func (c *CoreV1Client) Secrets(namespace string) SecretInterface {...}
//Services
func (c *CoreV1Client) Services(namespace string) ServiceInterface {...}
func (c *CoreV1Client) ServiceAccounts(namespace string) ServiceAccountInterface {...}
2.4.3 CoreV1Interface
k8s.io/client-go/kubernetes/typed/core/v1/core_client.go
type CoreV1Interface interface {
RESTClient() rest.Interface
ComponentStatusesGetter
ConfigMapsGetter
EndpointsGetter
EventsGetter
LimitRangesGetter
NamespacesGetter
NodesGetter
PersistentVolumesGetter
PersistentVolumeClaimsGetter
PodsGetter
PodTemplatesGetter
ReplicationControllersGetter
ResourceQuotasGetter
SecretsGetter
ServicesGetter
ServiceAccountsGetter
}
CoreV1Interface中包含了各种kubernetes对象的调用接口,例如PodsGetter是对kubernetes中pod对象增删改查操作的接口。ServicesGetter是对service对象的操作的接口。
2.4.4 PodsGetter
以下我们以PodsGetter接口为例分析CoreV1Client对pod对象的增删改查接口调用。
示例中的代码如下:
pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
CoreV1().Pods()
k8s.io/client-go/kubernetes/typed/core/v1/core_client.go
func (c *CoreV1Client) Pods(namespace string) PodInterface {
return newPods(c, namespace)
}
newPods()
k8s.io/client-go/kubernetes/typed/core/v1/pod.go
// newPods returns a Pods
func newPods(c *CoreV1Client, namespace string) *pods {
return &pods{
client: c.RESTClient(),
ns: namespace,
}
}
CoreV1().Pods()的方法实际上是调用了newPods()的方法,创建了一个pods对象,pods对象继承了rest.Interface接口,即最终的实现本质是RESTClient的HTTP调用。
k8s.io/client-go/kubernetes/typed/core/v1/pod.go
// pods implements PodInterface
type pods struct {
client rest.Interface
ns string
}
pods对象实现了PodInterface接口。PodInterface定义了pods对象的增删改查等方法。
k8s.io/client-go/kubernetes/typed/core/v1/pod.go
// PodInterface has methods to work with Pod resources.
type PodInterface interface {
Create(*v1.Pod) (*v1.Pod, error)
Update(*v1.Pod) (*v1.Pod, error)
UpdateStatus(*v1.Pod) (*v1.Pod, error)
Delete(name string, options *meta_v1.DeleteOptions) error
DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error
Get(name string, options meta_v1.GetOptions) (*v1.Pod, error)
List(opts meta_v1.ListOptions) (*v1.PodList, error)
Watch(opts meta_v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Pod, err error)
PodExpansion
}
PodsGetter
PodsGetter继承了PodInterface的接口。
k8s.io/client-go/kubernetes/typed/core/v1/pod.go
// PodsGetter has a method to return a PodInterface.
// A group's client should implement this interface.
type PodsGetter interface {
Pods(namespace string) PodInterface
}
Pods().List()
pods.List()方法通过RESTClient的HTTP调用来实现对kubernetes的pod资源的获取。
k8s.io/client-go/kubernetes/typed/core/v1/pod.go
// List takes label and field selectors, and returns the list of Pods that match those selectors.
func (c *pods) List(opts meta_v1.ListOptions) (result *v1.PodList, err error) {
result = &v1.PodList{}
err = c.client.Get().
Namespace(c.ns).
Resource("pods").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(result)
return
}
以上分析了clientset.CoreV1().Pods("").List(metav1.ListOptions{})对pod资源获取的过程,最终是调用RESTClient的方法实现。
2.5 RESTClient
以下分析RESTClient的创建过程及作用。
RESTClient对象的创建同样是依赖传入的config信息。
k8s.io/client-go/kubernetes/typed/core/v1/core_client.go
client, err := rest.RESTClientFor(&config)
2.5.1 rest.RESTClientFor
k8s.io/client-go/rest/config.go
// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
// object. Note that a RESTClient may require fields that are optional when initializing a Client.
// A RESTClient created by this method is generic - it expects to operate on an API that follows
// the Kubernetes conventions, but may not be the Kubernetes API.
func RESTClientFor(config *Config) (*RESTClient, error) {
...
qps := config.QPS
...
burst := config.Burst
...
baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
...
transport, err := TransportFor(config)
...
var httpClient *http.Client
if transport != http.DefaultTransport {
httpClient = &http.Client{Transport: transport}
if config.Timeout > 0 {
httpClient.Timeout = config.Timeout
}
}
return NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, qps, burst, config.RateLimiter, httpClient)
}
RESTClientFor函数调用了NewRESTClient的初始化函数。
2.5.2 NewRESTClient
k8s.io/client-go/rest/client.go
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
// such as Get, Put, Post, and Delete on specified paths. Codec controls encoding and
// decoding of responses from the server.
func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ContentConfig, maxQPS float32, maxBurst int, rateLimiter flowcontrol.RateLimiter, client *http.Client) (*RESTClient, error) {
base := *baseURL
...
serializers, err := createSerializers(config)
...
return &RESTClient{
base: &base,
versionedAPIPath: versionedAPIPath,
contentConfig: config,
serializers: *serializers,
createBackoffMgr: readExpBackoffConfig,
Throttle: throttle,
Client: client,
}, nil
}
2.5.3 RESTClient结构体
以下介绍RESTClient的结构体定义,RESTClient结构体中包含了http.Client,即本质上RESTClient就是一个http.Client的封装实现。
k8s.io/client-go/rest/client.go
// RESTClient imposes common Kubernetes API conventions on a set of resource paths.
// The baseURL is expected to point to an HTTP or HTTPS path that is the parent
// of one or more resources. The server should return a decodable API resource
// object, or an api.Status object which contains information about the reason for
// any failure.
//
// Most consumers should use client.New() to get a Kubernetes API client.
type RESTClient struct {
// base is the root URL for all invocations of the client
base *url.URL
// versionedAPIPath is a path segment connecting the base URL to the resource root
versionedAPIPath string
// contentConfig is the information used to communicate with the server.
contentConfig ContentConfig
// serializers contain all serializers for underlying content type.
serializers Serializers
// creates BackoffManager that is passed to requests.
createBackoffMgr func() BackoffManager
// TODO extract this into a wrapper interface via the RESTClient interface in kubectl.
Throttle flowcontrol.RateLimiter
// Set specific behavior of the client. If not set http.DefaultClient will be used.
Client *http.Client
}
2.5.4 RESTClient.Interface
RESTClient实现了以下的接口方法:
k8s.io/client-go/rest/client.go
// Interface captures the set of operations for generically interacting with Kubernetes REST apis.
type Interface interface {
GetRateLimiter() flowcontrol.RateLimiter
Verb(verb string) *Request
Post() *Request
Put() *Request
Patch(pt types.PatchType) *Request
Get() *Request
Delete() *Request
APIVersion() schema.GroupVersion
}
在调用HTTP方法(Post(),Put(),Get(),Delete() )时,实际上调用了Verb(verb string)函数。
k8s.io/client-go/rest/client.go
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
//
// Example usage of RESTClient's request building interface:
// c, err := NewRESTClient(...)
// if err != nil { ... }
// resp, err := c.Verb("GET").
// Path("pods").
// SelectorParam("labels", "area=staging").
// Timeout(10*time.Second).
// Do()
// if err != nil { ... }
// list, ok := resp.(*api.PodList)
//
func (c *RESTClient) Verb(verb string) *Request {
backoff := c.createBackoffMgr()
if c.Client == nil {
return NewRequest(nil, verb, c.base, c.versionedAPIPath, c.contentConfig, c.serializers, backoff, c.Throttle)
}
return NewRequest(c.Client, verb, c.base, c.versionedAPIPath, c.contentConfig, c.serializers, backoff, c.Throttle)
}
Verb函数调用了NewRequest方法,最后调用Do()方法实现一个HTTP请求获取Result。
2.6 总结
client-go对kubernetes资源对象的调用,需要先获取kubernetes的配置信息,即$HOME/.kube/config。
整个调用的过程如下:
kubeconfig→rest.config→clientset→具体的client(CoreV1Client)→具体的资源对象(pod)→RESTClient→http.Client→HTTP请求的发送及响应
通过clientset中不同的client和client中不同资源对象的方法实现对kubernetes中资源对象的增删改查等操作,常用的client有CoreV1Client、AppsV1beta1Client、ExtensionsV1beta1Client等。
3. client-go对k8s资源的调用
创建clientset
//获取kubeconfig
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
//创建config
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
//创建clientset
clientset, err := kubernetes.NewForConfig(config)
//具体的资源调用见以下例子
3.1 deployment
//声明deployment对象
var deployment *v1beta1.Deployment
//构造deployment对象
//创建deployment
deployment, err := clientset.AppsV1beta1().Deployments(<namespace>).Create(<deployment>)
//更新deployment
deployment, err := clientset.AppsV1beta1().Deployments(<namespace>).Update(<deployment>)
//删除deployment
err := clientset.AppsV1beta1().Deployments(<namespace>).Delete(<deployment.Name>, &meta_v1.DeleteOptions{})
//查询deployment
deployment, err := clientset.AppsV1beta1().Deployments(<namespace>).Get(<deployment.Name>, meta_v1.GetOptions{})
//列出deployment
deploymentList, err := clientset.AppsV1beta1().Deployments(<namespace>).List(&meta_v1.ListOptions{})
//watch deployment
watchInterface, err := clientset.AppsV1beta1().Deployments(<namespace>).Watch(&meta_v1.ListOptions{})
3.2 service
//声明service对象
var service *v1.Service
//构造service对象
//创建service
service, err := clientset.CoreV1().Services(<namespace>).Create(<service>)
//更新service
service, err := clientset.CoreV1().Services(<namespace>).Update(<service>)
//删除service
err := clientset.CoreV1().Services(<namespace>).Delete(<service.Name>, &meta_v1.DeleteOptions{})
//查询service
service, err := clientset.CoreV1().Services(<namespace>).Get(<service.Name>, meta_v1.GetOptions{})
//列出service
serviceList, err := clientset.CoreV1().Services(<namespace>).List(&meta_v1.ListOptions{})
//watch service
watchInterface, err := clientset.CoreV1().Services(<namespace>).Watch(&meta_v1.ListOptions{})
3.3 ingress
//声明ingress对象
var ingress *v1beta1.Ingress
//构造ingress对象
//创建ingress
ingress, err := clientset.ExtensionsV1beta1().Ingresses(<namespace>).Create(<ingress>)
//更新ingress
ingress, err := clientset.ExtensionsV1beta1().Ingresses(<namespace>).Update(<ingress>)
//删除ingress
err := clientset.ExtensionsV1beta1().Ingresses(<namespace>).Delete(<ingress.Name>, &meta_v1.DeleteOptions{})
//查询ingress
ingress, err := clientset.ExtensionsV1beta1().Ingresses(<namespace>).Get(<ingress.Name>, meta_v1.GetOptions{})
//列出ingress
ingressList, err := clientset.ExtensionsV1beta1().Ingresses(<namespace>).List(&meta_v1.ListOptions{})
//watch ingress
watchInterface, err := clientset.ExtensionsV1beta1().Ingresses(<namespace>).Watch(&meta_v1.ListOptions{})
3.4 replicaSet
//声明replicaSet对象
var replicaSet *v1beta1.ReplicaSet
//构造replicaSet对象
//创建replicaSet
replicaSet, err := clientset.ExtensionsV1beta1().ReplicaSets(<namespace>).Create(<replicaSet>)
//更新replicaSet
replicaSet, err := clientset.ExtensionsV1beta1().ReplicaSets(<namespace>).Update(<replicaSet>)
//删除replicaSet
err := clientset.ExtensionsV1beta1().ReplicaSets(<namespace>).Delete(<replicaSet.Name>, &meta_v1.DeleteOptions{})
//查询replicaSet
replicaSet, err := clientset.ExtensionsV1beta1().ReplicaSets(<namespace>).Get(<replicaSet.Name>, meta_v1.GetOptions{})
//列出replicaSet
replicaSetList, err := clientset.ExtensionsV1beta1().ReplicaSets(<namespace>).List(&meta_v1.ListOptions{})
//watch replicaSet
watchInterface, err := clientset.ExtensionsV1beta1().ReplicaSets(<namespace>).Watch(&meta_v1.ListOptions{})
新版的kubernetes中一般通过deployment来创建replicaSet,再通过replicaSet来控制pod。
3.5 pod
//声明pod对象
var pod *v1.Pod
//创建pod
pod, err := clientset.CoreV1().Pods(<namespace>).Create(<pod>)
//更新pod
pod, err := clientset.CoreV1().Pods(<namespace>).Update(<pod>)
//删除pod
err := clientset.CoreV1().Pods(<namespace>).Delete(<pod.Name>, &meta_v1.DeleteOptions{})
//查询pod
pod, err := clientset.CoreV1().Pods(<namespace>).Get(<pod.Name>, meta_v1.GetOptions{})
//列出pod
podList, err := clientset.CoreV1().Pods(<namespace>).List(&meta_v1.ListOptions{})
//watch pod
watchInterface, err := clientset.CoreV1().Pods(<namespace>).Watch(&meta_v1.ListOptions{})
3.6 statefulset
//声明statefulset对象
var statefulset *v1.StatefulSet
//创建statefulset
statefulset, err := clientset.AppsV1().StatefulSets(<namespace>).Create(<statefulset>)
//更新statefulset
statefulset, err := clientset.AppsV1().StatefulSets(<namespace>).Update(<statefulset>)
//删除statefulset
err := clientset.AppsV1().StatefulSets(<namespace>).Delete(<statefulset.Name>, &meta_v1.DeleteOptions{})
//查询statefulset
statefulset, err := clientset.AppsV1().StatefulSets(<namespace>).Get(<statefulset.Name>, meta_v1.GetOptions{})
//列出statefulset
statefulsetList, err := clientset.AppsV1().StatefulSets(<namespace>).List(&meta_v1.ListOptions{})
//watch statefulset
watchInterface, err := clientset.AppsV1().StatefulSets(<namespace>).Watch(&meta_v1.ListOptions{})
通过以上对kubernetes的资源对象的操作函数可以看出,每个资源对象都有增删改查等方法,基本调用逻辑类似。一般二次开发只需要创建deployment、service、ingress三个资源对象即可,pod对象由deployment包含的replicaSet来控制创建和删除。函数调用的入参一般只有NAMESPACE和kubernetesObject两个参数,部分操作有Options的参数。在创建前,需要对资源对象构造数据,可以理解为编辑一个资源对象的yaml文件,然后通过kubectl create -f xxx.yaml来创建对象。
参考文档:
9.2 - operator开发
9.2.1 - kubebuilder的使用
1. kubebuilder
1.1. 安装kubebuilder
# download kubebuilder and install locally.
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
1.2. kubebuilder命令
Development kit for building Kubernetes extensions and tools.
Provides libraries and tools to create new projects, APIs and controllers.
Includes tools for packaging artifacts into an installer container.
Typical project lifecycle:
- initialize a project:
kubebuilder init --domain example.com --license apache2 --owner "The Kubernetes authors"
- create one or more a new resource APIs and add your code to them:
kubebuilder create api --group <group> --version <version> --kind <Kind>
Create resource will prompt the user for if it should scaffold the Resource and / or Controller. To only
scaffold a Controller for an existing Resource, select "n" for Resource. To only define
the schema for a Resource without writing a Controller, select "n" for Controller.
After the scaffold is written, api will run make on the project.
Usage:
kubebuilder [command]
Available Commands:
create Scaffold a Kubernetes API or webhook.
edit This command will edit the project configuration
help Help about any command
init Initialize a new project
version Print the kubebuilder version
Flags:
-h, --help help for kubebuilder
Use "kubebuilder [command] --help" for more information about a command.
2. 操作步骤
2.1. 初始化
mkdir $GOPATH/src/github.com/huweihuang/operator-example
cd $GOPATH/src/github.com/huweihuang/operator-example
go mod init github.com/huweihuang/operator-example
2.2. 创建项目
# kubebuilder init --domain github.com --license apache2 --owner "Hu Weihuang"
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.5.0
Update go.mod:
$ go mod tidy
Running make:
$ make
go: creating new go.mod: module tmp
go: finding sigs.k8s.io v0.2.5
go: finding sigs.k8s.io/controller-tools/cmd v0.2.5
go: finding sigs.k8s.io/controller-tools/cmd/controller-gen v0.2.5
/Users/weihuanghu/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go
Next: define a resource with:
$ kubebuilder create api
查看生成文件:
./
├── Dockerfile
├── Makefile
├── PROJECT
├── bin
│ └── manager
├── config
│ ├── certmanager
│ │ ├── certificate.yaml
│ │ ├── kustomization.yaml
│ │ └── kustomizeconfig.yaml
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ ├── manager_webhook_patch.yaml
│ │ └── webhookcainjection_patch.yaml
│ ├── manager
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── auth_proxy_client_clusterrole.yaml
│ │ ├── auth_proxy_role.yaml
│ │ ├── auth_proxy_role_binding.yaml
│ │ ├── auth_proxy_service.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ └── role_binding.yaml
│ └── webhook
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ └── service.yaml
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
└── main.go
2.3. 创建API
# kubebuilder create api --group webapp --version v1 --kind Guestbook
Create Resource [y/n]
y
Create Controller [y/n]
y
Writing scaffold for you to edit...
api/v1/guestbook_types.go
controllers/guestbook_controller.go
Running make:
$ make
go: creating new go.mod: module tmp
go: finding sigs.k8s.io/controller-tools/cmd v0.2.5
go: finding sigs.k8s.io/controller-tools/cmd/controller-gen v0.2.5
go: finding sigs.k8s.io v0.2.5
/Users/weihuanghu/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go
查看创建文件
api
└── v1
├── groupversion_info.go
├── guestbook_types.go
└── zz_generated.deepcopy.go
controllers
├── guestbook_controller.go
└── suite_test.go
查看api/v1/guestbook_types.go
// GuestbookSpec defines the desired state of Guestbook
type GuestbookSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Quantity of instances
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=10
Size int32 `json:"size"`
// Name of the ConfigMap for GuestbookSpec's configuration
// +kubebuilder:validation:MaxLength=15
// +kubebuilder:validation:MinLength=1
ConfigMapName string `json:"configMapName"`
// +kubebuilder:validation:Enum=Phone;Address;Name
Type string `json:"alias,omitempty"`
}
// GuestbookStatus defines the observed state of Guestbook
type GuestbookStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// PodName of the active Guestbook node.
Active string `json:"active"`
// PodNames of the standby Guestbook nodes.
Standby []string `json:"standby"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// Guestbook is the Schema for the guestbooks API
type Guestbook struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec GuestbookSpec `json:"spec,omitempty"`
Status GuestbookStatus `json:"status,omitempty"`
}
3. troubleshooting
3.1. controller-gen: No such file or directory
➜ operator-example kubebuilder init --domain github.com --license apache2 --owner "Hu Weihuang"
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.5.0
Update go.mod:
$ go mod tidy
Running make:
$ make
go: creating new go.mod: module tmp
go: finding sigs.k8s.io v0.2.5
go: finding sigs.k8s.io/controller-tools/cmd v0.2.5
go: finding sigs.k8s.io/controller-tools/cmd/controller-gen v0.2.5
/Users/weihuanghu/go:/Users/weihuanghu/k8spath/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
/bin/sh: /Users/weihuanghu/go:/Users/weihuanghu/k8spath/bin/controller-gen: No such file or directory
make: *** [generate] Error 127
2020/04/13 14:34:47 failed to initialize project: exit status 2
由于本地存在多个GOPATH的目录,而获取了非当前项目下的GOPATH目录,因此将当前项目所在的GOPATH目录export到GOPATH环境变量中,就可以解决。
export GOPATH="/path/to/gopath"
参考:
9.2.2 - 如何开发一个Operator
开发一个k8s operator组件主要会用到以下几个仓库或工具:
-
kubebuilder/operator-sdk:主要用于创建CRD对象。
-
controller-manager:主要用于实现operator的controller逻辑。
本文以kubebuilder的工具和controller-manager example为例。
1. 准备工具环境及创建项目
安装kubebuilder工具
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
创建operator项目目录
$ kubebuilder init --domain example.com --license apache2 --owner "Your Name" --repo github.com/example/my-operator
将 example.com 替换为你的域名,Your Name 替换为你的名字,github.com/example/my-operator 替换为你的 GitHub 仓库。
2. 创建CRD对象
$ kubebuilder create api --group mygroup --version v1alpha1 --kind MyResource
这将在 api/v1alpha1 文件夹中创建一个名为 myresource_types.go 的文件,其中定义了 MyResource 资源的规范和状态。
编辑 api/v1alpha1/myresource_types.go 文件,添加自定义资源的规范和状态字段。
3. 实现控制器逻辑
在 controllers/myresource_controller.go 文件中实现控制器逻辑。该部分是不同的CRD实现自定义逻辑的核心部分。
主要包含以下几个部分:
-
定义ReconcileMyResource结构体。
-
实现
func (r *ReconcileMyResource) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error)函数接口。 -
定义finalizer逻辑。
-
定义reconcile逻辑。
package controllers
import (
"context"
"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/controller/inject"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
myv1alpha1 "github.com/example/my-operator/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)
var log = logf.Log.WithName("controller_myresource")
// Add creates a new MyResource Controller and adds it to the Manager. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
func Add(mgr manager.Manager) error {
return add(mgr, newReconciler(mgr))
}
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
return &ReconcileMyResource{client: mgr.GetClient(), scheme: mgr.GetScheme()}
}
// blank assignment to verify that ReconcileMyResource implements reconcile.Reconciler
var _ reconcile.Reconciler = &ReconcileMyResource{}
// ReconcileMyResource reconciles a MyResource object
type ReconcileMyResource struct {
client client.Client
scheme *runtime.Scheme
}
// Reconcile reads that state of the cluster for a MyResource object and makes changes based on the state read
// and what is in the MyResource.Spec
func (r *ReconcileMyResource) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
reqLogger := log.WithValues("Namespace", request.Namespace, "Name", request.Name)
reqLogger.Info("Reconciling MyResource")
// Fetch the MyResource instance
myresource := &myv1alpha1.MyResource{}
err := r.client.Get(ctx, request.NamespacedName, myresource)
if err != nil {
if errors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return reconcile.Result{}, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, err
}
// Add finalizer logic here
// Add reconcile logic here
return reconcile.Result{}, nil
}
4. 注册控制器
在 main.go 文件中注册控制器,主要包含以下几个步骤
-
ctrl.SetLogger(zap.New()):注册日志工具
-
ctrl.NewManager:创建一个manager,必要时可设置选主逻辑。
-
ctrl.NewControllerManagedBy(mgr):预计mgr创建manager controller并注册Reconciler。
-
mgr.Start(ctrl.SetupSignalHandler()): 运行mgr。
func main() {
// set logger
ctrl.SetLogger(zap.New())
// Set up a Manager
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: myv1alpha1.SchemeBuilder.Scheme,
MetricsBindAddress: *metricsHost,
Port: 9443,
LeaderElection: *enableLeaderElection,
LeaderElectionID: "my-operator-lock",
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
// in a real controller, we'd create a new scheme for this
err = api.AddToScheme(mgr.GetScheme())
if err != nil {
setupLog.Error(err, "unable to add scheme")
os.Exit(1)
}
// Create a new Reconciler
r := &ReconcileMyResource{
client: m.GetClient(),
scheme: m.GetScheme(),
}
// Create a new Controller and register the Reconciler
err = ctrl.NewControllerManagedBy(mgr).
For(&myv1alpha1.MyResource{}). // 自定义crd
Complete(r) // 注册Reconciler
if err != nil {
setupLog.Error(err, "unable to create controller")
os.Exit(1)
}
err = ctrl.NewWebhookManagedBy(mgr).
For(&api.ChaosPod{}).
Complete()
if err != nil {
setupLog.Error(err, "unable to create webhook")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
参考:
9.3 - CSI插件开发
9.3.1 - csi-provisioner源码分析
本文主要分析
csi-provisioner的源码,关于开发一个Dynamic Provisioner,具体可参考nfs-client-provisioner的源码分析
1. Dynamic Provisioner
1.1. Provisioner Interface
开发Dynamic Provisioner需要实现Provisioner接口,该接口有两个方法,分别是:
- Provision:创建存储资源,并且返回一个PV对象。
- Delete:移除对应的存储资源,但并没有删除PV对象。
1.2. 开发provisioner的步骤
- 写一个
provisioner实现Provisioner接口(包含Provision和Delete的方法)。 - 通过该
provisioner构建ProvisionController。 - 执行
ProvisionController的Run方法。
2. CSI Provisioner
CSI Provisioner的源码可参考:https://github.com/kubernetes-csi/external-provisioner。
2.1. Main 函数
2.1.1. 读取环境变量
源码如下:
var (
provisioner = flag.String("provisioner", "", "Name of the provisioner. The provisioner will only provision volumes for claims that request a StorageClass with a provisioner field set equal to this name.")
master = flag.String("master", "", "Master URL to build a client config from. Either this or kubeconfig needs to be set if the provisioner is being run out of cluster.")
kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file. Either this or master needs to be set if the provisioner is being run out of cluster.")
csiEndpoint = flag.String("csi-address", "/run/csi/socket", "The gRPC endpoint for Target CSI Volume")
connectionTimeout = flag.Duration("connection-timeout", 10*time.Second, "Timeout for waiting for CSI driver socket.")
volumeNamePrefix = flag.String("volume-name-prefix", "pvc", "Prefix to apply to the name of a created volume")
volumeNameUUIDLength = flag.Int("volume-name-uuid-length", -1, "Truncates generated UUID of a created volume to this length. Defaults behavior is to NOT truncate.")
showVersion = flag.Bool("version", false, "Show version.")
provisionController *controller.ProvisionController
version = "unknown"
)
func init() {
var config *rest.Config
var err error
flag.Parse()
flag.Set("logtostderr", "true")
if *showVersion {
fmt.Println(os.Args[0], version)
os.Exit(0)
}
glog.Infof("Version: %s", version)
...
}
通过init函数解析相关参数,其实provisioner指明为PVC提供PV的provisioner的名字,需要和StorageClass对象中的provisioner字段一致。
2.1.2. 获取clientset对象
源码如下:
// get the KUBECONFIG from env if specified (useful for local/debug cluster)
kubeconfigEnv := os.Getenv("KUBECONFIG")
if kubeconfigEnv != "" {
glog.Infof("Found KUBECONFIG environment variable set, using that..")
kubeconfig = &kubeconfigEnv
}
if *master != "" || *kubeconfig != "" {
glog.Infof("Either master or kubeconfig specified. building kube config from that..")
config, err = clientcmd.BuildConfigFromFlags(*master, *kubeconfig)
} else {
glog.Infof("Building kube configs for running in cluster...")
config, err = rest.InClusterConfig()
}
if err != nil {
glog.Fatalf("Failed to create config: %v", err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
glog.Fatalf("Failed to create client: %v", err)
}
// snapclientset.NewForConfig creates a new Clientset for VolumesnapshotV1alpha1Client
snapClient, err := snapclientset.NewForConfig(config)
if err != nil {
glog.Fatalf("Failed to create snapshot client: %v", err)
}
csiAPIClient, err := csiclientset.NewForConfig(config)
if err != nil {
glog.Fatalf("Failed to create CSI API client: %v", err)
}
通过读取对应的k8s的配置,创建clientset对象,用来执行k8s对应的API,其中主要包括对PV和PVC等对象的创建删除等操作。
2.1.3. k8s版本校验
// The controller needs to know what the server version is because out-of-tree
// provisioners aren't officially supported until 1.5
serverVersion, err := clientset.Discovery().ServerVersion()
if err != nil {
glog.Fatalf("Error getting server version: %v", err)
}
获取了k8s的版本信息,因为provisioners的功能在k8s 1.5及以上版本才支持。
2.1.4. 连接 csi socket
// Generate a unique ID for this provisioner
timeStamp := time.Now().UnixNano() / int64(time.Millisecond)
identity := strconv.FormatInt(timeStamp, 10) + "-" + strconv.Itoa(rand.Intn(10000)) + "-" + *provisioner
// Provisioner will stay in Init until driver opens csi socket, once it's done
// controller will exit this loop and proceed normally.
socketDown := true
grpcClient := &grpc.ClientConn{}
for socketDown {
grpcClient, err = ctrl.Connect(*csiEndpoint, *connectionTimeout)
if err == nil {
socketDown = false
continue
}
time.Sleep(10 * time.Second)
}
在Provisioner会停留在初始化状态,直到csi socket连接成功才正常运行。如果连接失败,会暂停10秒后重试,其中涉及以下2个参数:
- csiEndpoint:CSI Volume的gRPC地址,默认通过为
/run/csi/socket。 - connectionTimeout:连接CSI driver socket的超时时间,默认为10秒。
2.1.5. 构造csi-Provisioner对象
// Create the provisioner: it implements the Provisioner interface expected by
// the controller
csiProvisioner := ctrl.NewCSIProvisioner(clientset, csiAPIClient, *csiEndpoint, *connectionTimeout, identity, *volumeNamePrefix, *volumeNameUUIDLength, grpcClient, snapClient)
provisionController = controller.NewProvisionController(
clientset,
*provisioner,
csiProvisioner,
serverVersion.GitVersion,
)
通过参数clientset, csiAPIClient, csiEndpoint, connectionTimeout, identity, volumeNamePrefix, volumeNameUUIDLength, grpcClient, snapClient构造csi-Provisioner对象。
通过csiProvisioner构造ProvisionController对象。
2.1.6. 运行ProvisionController
func main() {
provisionController.Run(wait.NeverStop)
}
ProvisionController实现了具体的PV和PVC的相关逻辑,Run方法以常驻进程的方式运行。
2.2. Provision和Delete方法
2.2.1. Provision方法
csiProvisioner的Provision方法具体源码参考:https://github.com/kubernetes-csi/external-provisioner/blob/master/pkg/controller/controller.go#L336
Provision方法用来创建存储资源,并且返回一个PV对象。其中入参是VolumeOptions,用来指定PV对象的相关属性。
1、构造PV相关属性
pvName, err := makeVolumeName(p.volumeNamePrefix, fmt.Sprintf("%s", options.PVC.ObjectMeta.UID), p.volumeNameUUIDLength)
if err != nil {
return nil, err
}
2、构造CSIPersistentVolumeSource相关属性
driverState, err := checkDriverState(p.grpcClient, p.timeout, needSnapshotSupport)
if err != nil {
return nil, err
}
...
// Resolve controller publish, node stage, node publish secret references
controllerPublishSecretRef, err := getSecretReference(controllerPublishSecretNameKey, controllerPublishSecretNamespaceKey, options.Parameters, pvName, options.PVC)
if err != nil {
return nil, err
}
nodeStageSecretRef, err := getSecretReference(nodeStageSecretNameKey, nodeStageSecretNamespaceKey, options.Parameters, pvName, options.PVC)
if err != nil {
return nil, err
}
nodePublishSecretRef, err := getSecretReference(nodePublishSecretNameKey, nodePublishSecretNamespaceKey, options.Parameters, pvName, options.PVC)
if err != nil {
return nil, err
}
...
volumeAttributes := map[string]string{provisionerIDKey: p.identity}
for k, v := range rep.Volume.Attributes {
volumeAttributes[k] = v
}
...
fsType := ""
for k, v := range options.Parameters {
switch strings.ToLower(k) {
case "fstype":
fsType = v
}
}
if len(fsType) == 0 {
fsType = defaultFSType
}
3、创建CSI CreateVolumeRequest
// Create a CSI CreateVolumeRequest and Response
req := csi.CreateVolumeRequest{
Name: pvName,
Parameters: options.Parameters,
VolumeCapabilities: volumeCaps,
CapacityRange: &csi.CapacityRange{
RequiredBytes: int64(volSizeBytes),
},
}
...
glog.V(5).Infof("CreateVolumeRequest %+v", req)
rep := &csi.CreateVolumeResponse{}
...
opts := wait.Backoff{Duration: backoffDuration, Factor: backoffFactor, Steps: backoffSteps}
err = wait.ExponentialBackoff(opts, func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()
rep, err = p.csiClient.CreateVolume(ctx, &req)
if err == nil {
// CreateVolume has finished successfully
return true, nil
}
if status, ok := status.FromError(err); ok {
if status.Code() == codes.DeadlineExceeded {
// CreateVolume timed out, give it another chance to complete
glog.Warningf("CreateVolume timeout: %s has expired, operation will be retried", p.timeout.String())
return false, nil
}
}
// CreateVolume failed , no reason to retry, bailing from ExponentialBackoff
return false, err
})
if err != nil {
return nil, err
}
if rep.Volume != nil {
glog.V(3).Infof("create volume rep: %+v", *rep.Volume)
}
respCap := rep.GetVolume().GetCapacityBytes()
if respCap < volSizeBytes {
capErr := fmt.Errorf("created volume capacity %v less than requested capacity %v", respCap, volSizeBytes)
delReq := &csi.DeleteVolumeRequest{
VolumeId: rep.GetVolume().GetId(),
}
delReq.ControllerDeleteSecrets = provisionerCredentials
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()
_, err := p.csiClient.DeleteVolume(ctx, delReq)
if err != nil {
capErr = fmt.Errorf("%v. Cleanup of volume %s failed, volume is orphaned: %v", capErr, pvName, err)
}
return nil, capErr
}
Provison方法核心功能是调用p.csiClient.CreateVolume(ctx, &req)。
4、构造PV对象
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: pvName,
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
AccessModes: options.PVC.Spec.AccessModes,
Capacity: v1.ResourceList{
v1.ResourceName(v1.ResourceStorage): bytesToGiQuantity(respCap),
},
// TODO wait for CSI VolumeSource API
PersistentVolumeSource: v1.PersistentVolumeSource{
CSI: &v1.CSIPersistentVolumeSource{
Driver: driverState.driverName,
VolumeHandle: p.volumeIdToHandle(rep.Volume.Id),
FSType: fsType,
VolumeAttributes: volumeAttributes,
ControllerPublishSecretRef: controllerPublishSecretRef,
NodeStageSecretRef: nodeStageSecretRef,
NodePublishSecretRef: nodePublishSecretRef,
},
},
},
}
if driverState.capabilities.Has(PluginCapability_ACCESSIBILITY_CONSTRAINTS) {
pv.Spec.NodeAffinity = GenerateVolumeNodeAffinity(rep.Volume.AccessibleTopology)
}
glog.Infof("successfully created PV %+v", pv.Spec.PersistentVolumeSource)
return pv, nil
Provision方法只是通过VolumeOptions参数来构建PV对象,并没有执行具体PV的创建或删除的操作。
不同类型的Provisioner的,一般是PersistentVolumeSource类型和参数不同,例如csi-provisioner对应的PersistentVolumeSource为CSI,并且需要传入CSI相关的参数:
DriverVolumeHandleFSTypeVolumeAttributesControllerPublishSecretRefNodeStageSecretRefNodePublishSecretRef
2.2.2. Delete方法
csiProvisioner的delete方法具体源码参考:https://github.com/kubernetes-csi/external-provisioner/blob/master/pkg/controller/controller.go#L606
func (p *csiProvisioner) Delete(volume *v1.PersistentVolume) error {
if volume == nil || volume.Spec.CSI == nil {
return fmt.Errorf("invalid CSI PV")
}
volumeId := p.volumeHandleToId(volume.Spec.CSI.VolumeHandle)
_, err := checkDriverState(p.grpcClient, p.timeout, false)
if err != nil {
return err
}
req := csi.DeleteVolumeRequest{
VolumeId: volumeId,
}
// get secrets if StorageClass specifies it
storageClassName := volume.Spec.StorageClassName
if len(storageClassName) != 0 {
if storageClass, err := p.client.StorageV1().StorageClasses().Get(storageClassName, metav1.GetOptions{}); err == nil {
// Resolve provision secret credentials.
// No PVC is provided when resolving provision/delete secret names, since the PVC may or may not exist at delete time.
provisionerSecretRef, err := getSecretReference(provisionerSecretNameKey, provisionerSecretNamespaceKey, storageClass.Parameters, volume.Name, nil)
if err != nil {
return err
}
credentials, err := getCredentials(p.client, provisionerSecretRef)
if err != nil {
return err
}
req.ControllerDeleteSecrets = credentials
}
}
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()
_, err = p.csiClient.DeleteVolume(ctx, &req)
return err
}
Delete方法主要是调用了p.csiClient.DeleteVolume(ctx, &req)方法。
2.3. 总结
csi provisioner实现了Provisioner接口,其中包含Provison和Delete两个方法:
Provision:调用csiClient.CreateVolume方法,同时构造并返回PV对象。Delete:调用csiClient.DeleteVolume方法。
csi provisioner的核心方法都调用了csi-client相关方法。
3. csi-client
csi client的相关代码参考:https://github.com/container-storage-interface/spec/blob/master/lib/go/csi/v0/csi.pb.go
3.1. 构造csi-client
3.1.1. 构造grpcClient
// Provisioner will stay in Init until driver opens csi socket, once it's done
// controller will exit this loop and proceed normally.
socketDown := true
grpcClient := &grpc.ClientConn{}
for socketDown {
grpcClient, err = ctrl.Connect(*csiEndpoint, *connectionTimeout)
if err == nil {
socketDown = false
continue
}
time.Sleep(10 * time.Second)
}
通过连接csi socket,连接成功才构造可用的grpcClient。
3.1.2. 构造csi-client
通过grpcClient构造csi-client。
// Create the provisioner: it implements the Provisioner interface expected by
// the controller
csiProvisioner := ctrl.NewCSIProvisioner(clientset, csiAPIClient, *csiEndpoint, *connectionTimeout, identity, *volumeNamePrefix, *volumeNameUUIDLength, grpcClient, snapClient)
NewCSIProvisioner
// NewCSIProvisioner creates new CSI provisioner
func NewCSIProvisioner(client kubernetes.Interface,
csiAPIClient csiclientset.Interface,
csiEndpoint string,
connectionTimeout time.Duration,
identity string,
volumeNamePrefix string,
volumeNameUUIDLength int,
grpcClient *grpc.ClientConn,
snapshotClient snapclientset.Interface) controller.Provisioner {
csiClient := csi.NewControllerClient(grpcClient)
provisioner := &csiProvisioner{
client: client,
grpcClient: grpcClient,
csiClient: csiClient,
csiAPIClient: csiAPIClient,
snapshotClient: snapshotClient,
timeout: connectionTimeout,
identity: identity,
volumeNamePrefix: volumeNamePrefix,
volumeNameUUIDLength: volumeNameUUIDLength,
}
return provisioner
}
csiClient := csi.NewControllerClient(grpcClient)
...
type controllerClient struct {
cc *grpc.ClientConn
}
func NewControllerClient(cc *grpc.ClientConn) ControllerClient {
return &controllerClient{cc}
}
3.2. csiClient.CreateVolume
csi provisoner中调用csiClient.CreateVolume代码如下:
opts := wait.Backoff{Duration: backoffDuration, Factor: backoffFactor, Steps: backoffSteps}
err = wait.ExponentialBackoff(opts, func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()
rep, err = p.csiClient.CreateVolume(ctx, &req)
if err == nil {
// CreateVolume has finished successfully
return true, nil
}
if status, ok := status.FromError(err); ok {
if status.Code() == codes.DeadlineExceeded {
// CreateVolume timed out, give it another chance to complete
glog.Warningf("CreateVolume timeout: %s has expired, operation will be retried", p.timeout.String())
return false, nil
}
}
// CreateVolume failed , no reason to retry, bailing from ExponentialBackoff
return false, err
})
CreateVolumeRequest的构造:
// Create a CSI CreateVolumeRequest and Response
req := csi.CreateVolumeRequest{
Name: pvName,
Parameters: options.Parameters,
VolumeCapabilities: volumeCaps,
CapacityRange: &csi.CapacityRange{
RequiredBytes: int64(volSizeBytes),
},
}
...
req.VolumeContentSource = volumeContentSource
...
req.AccessibilityRequirements = requirements
...
req.ControllerCreateSecrets = provisionerCredentials
具体的Create实现方法如下:
其中
csiClient是个接口类型
具体代码参考controllerClient.CreateVolume
func (c *controllerClient) CreateVolume(ctx context.Context, in *CreateVolumeRequest, opts ...grpc.CallOption) (*CreateVolumeResponse, error) {
out := new(CreateVolumeResponse)
err := grpc.Invoke(ctx, "/csi.v0.Controller/CreateVolume", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
3.3. csiClient.DeleteVolume
csi provisoner中调用csiClient.DeleteVolume代码如下:
func (p *csiProvisioner) Delete(volume *v1.PersistentVolume) error {
...
req := csi.DeleteVolumeRequest{
VolumeId: volumeId,
}
// get secrets if StorageClass specifies it
...
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()
_, err = p.csiClient.DeleteVolume(ctx, &req)
return err
}
DeleteVolumeRequest的构造:
req := csi.DeleteVolumeRequest{
VolumeId: volumeId,
}
...
req.ControllerDeleteSecrets = credentials
将构造的DeleteVolumeRequest传给DeleteVolume方法。
具体的Delete实现方法如下:
具体代码参考:controllerClient.DeleteVolume
func (c *controllerClient) DeleteVolume(ctx context.Context, in *DeleteVolumeRequest, opts ...grpc.CallOption) (*DeleteVolumeResponse, error) {
out := new(DeleteVolumeResponse)
err := grpc.Invoke(ctx, "/csi.v0.Controller/DeleteVolume", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
4. ProvisionController.Run
自定义的provisioner实现了Provisoner接口的Provision和Delete方法,这两个方法主要对后端存储做创建和删除操作,并没有对PV对象进行创建和删除操作。
PV对象的相关操作具体由ProvisionController中的provisionClaimOperation和deleteVolumeOperation具体执行,同时调用了具体provisioner的Provision和Delete两个方法来对存储数据做处理。
func main() {
provisionController.Run(wait.NeverStop)
}
这块代码逻辑可参考:nfs-client-provisioner 源码分析
参考文章:
9.3.2 - nfs-client-provisioner源码分析
如果要开发一个
Dynamic Provisioner,需要使用到the helper library。
1. Dynamic Provisioner
1.1. Provisioner Interface
开发Dynamic Provisioner需要实现Provisioner接口,该接口有两个方法,分别是:
- Provision:创建存储资源,并且返回一个PV对象。
- Delete:移除对应的存储资源,但并没有删除PV对象。
Provisioner 接口源码如下:
// Provisioner is an interface that creates templates for PersistentVolumes
// and can create the volume as a new resource in the infrastructure provider.
// It can also remove the volume it created from the underlying storage
// provider.
type Provisioner interface {
// Provision creates a volume i.e. the storage asset and returns a PV object
// for the volume
Provision(VolumeOptions) (*v1.PersistentVolume, error)
// Delete removes the storage asset that was created by Provision backing the
// given PV. Does not delete the PV object itself.
//
// May return IgnoredError to indicate that the call has been ignored and no
// action taken.
Delete(*v1.PersistentVolume) error
}
1.2. VolumeOptions
Provisioner接口的Provision方法的入参是一个VolumeOptions对象。VolumeOptions对象包含了创建PV对象所需要的信息,例如:PV的回收策略,PV的名字,PV所对应的PVC对象以及PVC的StorageClass对象使用的参数等。
VolumeOptions 源码如下:
// VolumeOptions contains option information about a volume
// https://github.com/kubernetes/kubernetes/blob/release-1.4/pkg/volume/plugins.go
type VolumeOptions struct {
// Reclamation policy for a persistent volume
PersistentVolumeReclaimPolicy v1.PersistentVolumeReclaimPolicy
// PV.Name of the appropriate PersistentVolume. Used to generate cloud
// volume name.
PVName string
// PV mount options. Not validated - mount of the PVs will simply fail if one is invalid.
MountOptions []string
// PVC is reference to the claim that lead to provisioning of a new PV.
// Provisioners *must* create a PV that would be matched by this PVC,
// i.e. with required capacity, accessMode, labels matching PVC.Selector and
// so on.
PVC *v1.PersistentVolumeClaim
// Volume provisioning parameters from StorageClass
Parameters map[string]string
// Node selected by the scheduler for the volume.
SelectedNode *v1.Node
// Topology constraint parameter from StorageClass
AllowedTopologies []v1.TopologySelectorTerm
}
1.3. ProvisionController
ProvisionController是一个给PVC提供PV的控制器,具体执行Provisioner接口的Provision和Delete的方法的所有逻辑。
1.4. 开发provisioner的步骤
- 写一个
provisioner实现Provisioner接口(包含Provision和Delete的方法)。 - 通过该
provisioner构建ProvisionController。 - 执行
ProvisionController的Run方法。
2. NFS Client Provisioner
nfs-client-provisioner是一个automatic provisioner,使用NFS作为存储,自动创建PV和对应的PVC,本身不提供NFS存储,需要外部先有一套NFS存储服务。
- PV以
${namespace}-${pvcName}-${pvName}的命名格式提供(在NFS服务器上) - PV回收的时候以
archieved-${namespace}-${pvcName}-${pvName}的命名格式(在NFS服务器上)
以下通过nfs-client-provisioner的源码分析来说明开发自定义provisioner整个过程。nfs-client-provisioner的主要代码都在provisioner.go的文件中。
nfs-client-provisioner源码地址:https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client
2.1. Main函数
2.1.1. 读取环境变量
源码如下:
func main() {
flag.Parse()
flag.Set("logtostderr", "true")
server := os.Getenv("NFS_SERVER")
if server == "" {
glog.Fatal("NFS_SERVER not set")
}
path := os.Getenv("NFS_PATH")
if path == "" {
glog.Fatal("NFS_PATH not set")
}
provisionerName := os.Getenv(provisionerNameKey)
if provisionerName == "" {
glog.Fatalf("environment variable %s is not set! Please set it.", provisionerNameKey)
}
...
}
main函数先获取NFS_SERVER、NFS_PATH、PROVISIONER_NAME三个环境变量的值,因此在部署nfs-client-provisioner的时候,需要将这三个环境变量的值传入。
NFS_SERVER:NFS服务端的IP地址。NFS_PATH:NFS服务端设置的共享目录PROVISIONER_NAME:provisioner的名字,需要和StorageClass对象中的provisioner字段一致。
例如StorageClass对象的yaml文件如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false" # When set to "false" your PVs will not be archived by the provisioner upon deletion of the PVC.
2.1.2. 获取clientset对象
源码如下:
// Create an InClusterConfig and use it to create a client for the controller
// to use to communicate with Kubernetes
config, err := rest.InClusterConfig()
if err != nil {
glog.Fatalf("Failed to create config: %v", err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
glog.Fatalf("Failed to create client: %v", err)
}
通过读取对应的k8s的配置,创建clientset对象,用来执行k8s对应的API,其中主要包括对PV和PVC等对象的创建删除等操作。
2.1.3. 构造nfsProvisioner对象
源码如下:
// The controller needs to know what the server version is because out-of-tree
// provisioners aren't officially supported until 1.5
serverVersion, err := clientset.Discovery().ServerVersion()
if err != nil {
glog.Fatalf("Error getting server version: %v", err)
}
clientNFSProvisioner := &nfsProvisioner{
client: clientset,
server: server,
path: path,
}
通过clientset、server、path等值构造nfsProvisioner对象,同时还获取了k8s的版本信息,因为provisioners的功能在k8s 1.5及以上版本才支持。
nfsProvisioner类型定义如下:
type nfsProvisioner struct {
client kubernetes.Interface
server string
path string
}
var _ controller.Provisioner = &nfsProvisioner{}
nfsProvisioner是一个自定义的provisioner,用来实现Provisioner的接口,其中的属性除了server、path这两个关于NFS相关的参数,还包含了client,主要用来调用k8s的API。
var _ controller.Provisioner = &nfsProvisioner{}
以上用法用来检测nfsProvisioner是否实现了Provisioner的接口。
2.1.4. 构建并运行ProvisionController
源码如下:
// Start the provision controller which will dynamically provision efs NFS
// PVs
pc := controller.NewProvisionController(clientset, provisionerName, clientNFSProvisioner, serverVersion.GitVersion)
pc.Run(wait.NeverStop)
通过nfsProvisioner构造ProvisionController对象并执行Run方法,ProvisionController实现了具体的PV和PVC的相关逻辑,Run方法以常驻进程的方式运行。
2.2. Provision和Delete方法
2.2.1. Provision方法
nfsProvisioner的Provision方法具体源码参考:https://github.com/kubernetes-incubator/external-storage/blob/master/nfs-client/cmd/nfs-client-provisioner/provisioner.go#L56
Provision方法用来创建存储资源,并且返回一个PV对象。其中入参是VolumeOptions,用来指定PV对象的相关属性。
1、构建PV和PVC的名称
func (p *nfsProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
if options.PVC.Spec.Selector != nil {
return nil, fmt.Errorf("claim Selector is not supported")
}
glog.V(4).Infof("nfs provisioner: VolumeOptions %v", options)
pvcNamespace := options.PVC.Namespace
pvcName := options.PVC.Name
pvName := strings.Join([]string{pvcNamespace, pvcName, options.PVName}, "-")
fullPath := filepath.Join(mountPath, pvName)
glog.V(4).Infof("creating path %s", fullPath)
if err := os.MkdirAll(fullPath, 0777); err != nil {
return nil, errors.New("unable to create directory to provision new pv: " + err.Error())
}
os.Chmod(fullPath, 0777)
path := filepath.Join(p.path, pvName)
...
}
通过VolumeOptions的入参,构建PV和PVC的名称,以及创建路径path。
2、构造PV对象
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: options.PVName,
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
AccessModes: options.PVC.Spec.AccessModes,
MountOptions: options.MountOptions,
Capacity: v1.ResourceList{
v1.ResourceName(v1.ResourceStorage): options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)],
},
PersistentVolumeSource: v1.PersistentVolumeSource{
NFS: &v1.NFSVolumeSource{
Server: p.server,
Path: path,
ReadOnly: false,
},
},
},
}
return pv, nil
综上可以看出,Provision方法只是通过VolumeOptions参数来构建PV对象,并没有执行具体PV的创建或删除的操作。
不同类型的Provisioner的,一般是PersistentVolumeSource类型和参数不同,例如nfs-provisioner对应的PersistentVolumeSource为NFS,并且需要传入NFS相关的参数:Server,Path等。
2.2.2. Delete方法
nfsProvisioner的delete方法具体源码参考:https://github.com/kubernetes-incubator/external-storage/blob/master/nfs-client/cmd/nfs-client-provisioner/provisioner.go#L99
1、获取pvName和path等相关参数
func (p *nfsProvisioner) Delete(volume *v1.PersistentVolume) error {
path := volume.Spec.PersistentVolumeSource.NFS.Path
pvName := filepath.Base(path)
oldPath := filepath.Join(mountPath, pvName)
if _, err := os.Stat(oldPath); os.IsNotExist(err) {
glog.Warningf("path %s does not exist, deletion skipped", oldPath)
return nil
}
...
}
通过path和pvName生成oldPath,其中oldPath是原先NFS服务器上pod对应的数据持久化存储路径。
2、获取archiveOnDelete参数并删除数据
// Get the storage class for this volume.
storageClass, err := p.getClassForVolume(volume)
if err != nil {
return err
}
// Determine if the "archiveOnDelete" parameter exists.
// If it exists and has a falsey value, delete the directory.
// Otherwise, archive it.
archiveOnDelete, exists := storageClass.Parameters["archiveOnDelete"]
if exists {
archiveBool, err := strconv.ParseBool(archiveOnDelete)
if err != nil {
return err
}
if !archiveBool {
return os.RemoveAll(oldPath)
}
}
如果storageClass对象中指定archiveOnDelete参数并且值为false,则会自动删除oldPath下的所有数据,即pod对应的数据持久化存储数据。
archiveOnDelete字面意思为删除时是否存档,false表示不存档,即删除数据,true表示存档,即重命名路径。
3、重命名旧数据路径
archivePath := filepath.Join(mountPath, "archived-"+pvName)
glog.V(4).Infof("archiving path %s to %s", oldPath, archivePath)
return os.Rename(oldPath, archivePath)
如果storageClass对象中没有指定archiveOnDelete参数或者值为true,表明需要删除时存档,即将oldPath重命名,命名格式为oldPath前面增加archived-的前缀。
3. ProvisionController
3.1. ProvisionController结构体
源码具体参考:https://github.com/kubernetes-incubator/external-storage/blob/master/lib/controller/controller.go#L82
ProvisionController是一个给PVC提供PV的控制器,具体执行Provisioner接口的Provision和Delete的方法的所有逻辑。
3.1.1. 入参
// ProvisionController is a controller that provisions PersistentVolumes for
// PersistentVolumeClaims.
type ProvisionController struct {
client kubernetes.Interface
// The name of the provisioner for which this controller dynamically
// provisions volumes. The value of annDynamicallyProvisioned and
// annStorageProvisioner to set & watch for, respectively
provisionerName string
// The provisioner the controller will use to provision and delete volumes.
// Presumably this implementer of Provisioner carries its own
// volume-specific options and such that it needs in order to provision
// volumes.
provisioner Provisioner
// Kubernetes cluster server version:
// * 1.4: storage classes introduced as beta. Technically out-of-tree dynamic
// provisioning is not officially supported, though it works
// * 1.5: storage classes stay in beta. Out-of-tree dynamic provisioning is
// officially supported
// * 1.6: storage classes enter GA
kubeVersion *utilversion.Version
...
}
client、provisionerName、provisioner、kubeVersion等属性作为NewProvisionController的入参。
client:clientset客户端,用来调用k8s的API。provisionerName:provisioner的名字,需要和StorageClass对象中的provisioner字段一致。provisioner:具体的provisioner的实现者,本文为nfsProvisioner。kubeVersion:k8s的版本信息。
3.1.2. Controller和Informer
type ProvisionController struct {
...
claimInformer cache.SharedInformer
claims cache.Store
claimController cache.Controller
volumeInformer cache.SharedInformer
volumes cache.Store
volumeController cache.Controller
classInformer cache.SharedInformer
classes cache.Store
classController cache.Controller
...
}
ProvisionController结构体中包含了PV、PVC、StorageClass三个对象的Controller、Informer和Store,主要用来执行这三个对象的相关操作。
- Controller:通用的控制框架
- Informer:消息通知器
- Store:通用的对象存储接口
3.1.3. workqueue
type ProvisionController struct {
...
claimQueue workqueue.RateLimitingInterface
volumeQueue workqueue.RateLimitingInterface
...
}
claimQueue和volumeQueue分别是PV和PVC的任务队列。
3.1.4. 其他
// Identity of this controller, generated at creation time and not persisted
// across restarts. Useful only for debugging, for seeing the source of
// events. controller.provisioner may have its own, different notion of
// identity which may/may not persist across restarts
id string
component string
eventRecorder record.EventRecorder
resyncPeriod time.Duration
exponentialBackOffOnError bool
threadiness int
createProvisionedPVRetryCount int
createProvisionedPVInterval time.Duration
failedProvisionThreshold, failedDeleteThreshold int
// The port for metrics server to serve on.
metricsPort int32
// The IP address for metrics server to serve on.
metricsAddress string
// The path of metrics endpoint path.
metricsPath string
// Parameters of leaderelection.LeaderElectionConfig.
leaseDuration, renewDeadline, retryPeriod time.Duration
hasRun bool
hasRunLock *sync.Mutex
3.2. NewProvisionController方法
源码地址:https://github.com/kubernetes-incubator/external-storage/blob/master/lib/controller/controller.go#L418
NewProvisionController方法主要用来构造ProvisionController。
3.2.1. 初始化默认值
// NewProvisionController creates a new provision controller using
// the given configuration parameters and with private (non-shared) informers.
func NewProvisionController(
client kubernetes.Interface,
provisionerName string,
provisioner Provisioner,
kubeVersion string,
options ...func(*ProvisionController) error,
) *ProvisionController {
...
controller := &ProvisionController{
client: client,
provisionerName: provisionerName,
provisioner: provisioner,
kubeVersion: utilversion.MustParseSemantic(kubeVersion),
id: id,
component: component,
eventRecorder: eventRecorder,
resyncPeriod: DefaultResyncPeriod,
exponentialBackOffOnError: DefaultExponentialBackOffOnError,
threadiness: DefaultThreadiness,
createProvisionedPVRetryCount: DefaultCreateProvisionedPVRetryCount,
createProvisionedPVInterval: DefaultCreateProvisionedPVInterval,
failedProvisionThreshold: DefaultFailedProvisionThreshold,
failedDeleteThreshold: DefaultFailedDeleteThreshold,
leaseDuration: DefaultLeaseDuration,
renewDeadline: DefaultRenewDeadline,
retryPeriod: DefaultRetryPeriod,
metricsPort: DefaultMetricsPort,
metricsAddress: DefaultMetricsAddress,
metricsPath: DefaultMetricsPath,
hasRun: false,
hasRunLock: &sync.Mutex{},
}
...
}
3.2.2. 初始化任务队列
ratelimiter := workqueue.NewMaxOfRateLimiter(
workqueue.NewItemExponentialFailureRateLimiter(15*time.Second, 1000*time.Second),
&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
)
if !controller.exponentialBackOffOnError {
ratelimiter = workqueue.NewMaxOfRateLimiter(
workqueue.NewItemExponentialFailureRateLimiter(15*time.Second, 15*time.Second),
&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
)
}
controller.claimQueue = workqueue.NewNamedRateLimitingQueue(ratelimiter, "claims")
controller.volumeQueue = workqueue.NewNamedRateLimitingQueue(ratelimiter, "volumes")
3.2.3. ListWatch
// PVC
claimSource := &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
return client.CoreV1().PersistentVolumeClaims(v1.NamespaceAll).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return client.CoreV1().PersistentVolumeClaims(v1.NamespaceAll).Watch(options)
},
}
// PV
volumeSource := &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
return client.CoreV1().PersistentVolumes().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return client.CoreV1().PersistentVolumes().Watch(options)
},
}
// StorageClass
classSource = &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
return client.StorageV1().StorageClasses().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return client.StorageV1().StorageClasses().Watch(options)
},
}
list-watch机制是k8s中用来监听对象变化的核心机制,ListWatch包含ListFunc和WatchFunc两个函数,且不能为空,以上代码分别构造了PV、PVC、StorageClass三个对象的ListWatch结构体。该机制的实现在client-go的cache包中,具体参考:https://godoc.org/k8s.io/client-go/tools/cache。
更多ListWatch代码如下:
具体参考:https://github.com/kubernetes-incubator/external-storage/blob/89b0aaf6413b249b37834b124fc314ef7b8ee949/vendor/k8s.io/client-go/tools/cache/listwatch.go#L34
// ListerWatcher is any object that knows how to perform an initial list and start a watch on a resource.
type ListerWatcher interface {
// List should return a list type object; the Items field will be extracted, and the
// ResourceVersion field will be used to start the watch in the right place.
List(options metav1.ListOptions) (runtime.Object, error)
// Watch should begin a watch at the specified version.
Watch(options metav1.ListOptions) (watch.Interface, error)
}
// ListFunc knows how to list resources
type ListFunc func(options metav1.ListOptions) (runtime.Object, error)
// WatchFunc knows how to watch resources
type WatchFunc func(options metav1.ListOptions) (watch.Interface, error)
// ListWatch knows how to list and watch a set of apiserver resources. It satisfies the ListerWatcher interface.
// It is a convenience function for users of NewReflector, etc.
// ListFunc and WatchFunc must not be nil
type ListWatch struct {
ListFunc ListFunc
WatchFunc WatchFunc
// DisableChunking requests no chunking for this list watcher.
DisableChunking bool
}
3.2.4. ResourceEventHandlerFuncs
// PVC
claimHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
UpdateFunc: func(oldObj, newObj interface{}) { controller.enqueueWork(controller.claimQueue, newObj) },
DeleteFunc: func(obj interface{}) { controller.forgetWork(controller.claimQueue, obj) },
}
// PV
volumeHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
UpdateFunc: func(oldObj, newObj interface{}) { controller.enqueueWork(controller.volumeQueue, newObj) },
DeleteFunc: func(obj interface{}) { controller.forgetWork(controller.volumeQueue, obj) },
}
// StorageClass
classHandler := cache.ResourceEventHandlerFuncs{
// We don't need an actual event handler for StorageClasses,
// but we must pass a non-nil one to cache.NewInformer()
AddFunc: nil,
UpdateFunc: nil,
DeleteFunc: nil,
}
ResourceEventHandlerFuncs是资源事件处理函数,主要用来对k8s资源对象增删改变化的事件进行消息通知,该函数实现了ResourceEventHandler的接口。具体代码逻辑在client-go的cache包中。
更多ResourceEventHandlerFuncs代码可参考:
// ResourceEventHandler can handle notifications for events that happen to a
// resource. The events are informational only, so you can't return an
// error.
// * OnAdd is called when an object is added.
// * OnUpdate is called when an object is modified. Note that oldObj is the
// last known state of the object-- it is possible that several changes
// were combined together, so you can't use this to see every single
// change. OnUpdate is also called when a re-list happens, and it will
// get called even if nothing changed. This is useful for periodically
// evaluating or syncing something.
// * OnDelete will get the final state of the item if it is known, otherwise
// it will get an object of type DeletedFinalStateUnknown. This can
// happen if the watch is closed and misses the delete event and we don't
// notice the deletion until the subsequent re-list.
type ResourceEventHandler interface {
OnAdd(obj interface{})
OnUpdate(oldObj, newObj interface{})
OnDelete(obj interface{})
}
// ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or
// as few of the notification functions as you want while still implementing
// ResourceEventHandler.
type ResourceEventHandlerFuncs struct {
AddFunc func(obj interface{})
UpdateFunc func(oldObj, newObj interface{})
DeleteFunc func(obj interface{})
}
3.2.5. 构造Store和Controller
1、PVC
if controller.claimInformer != nil {
controller.claimInformer.AddEventHandlerWithResyncPeriod(claimHandler, controller.resyncPeriod)
controller.claims, controller.claimController =
controller.claimInformer.GetStore(),
controller.claimInformer.GetController()
} else {
controller.claims, controller.claimController =
cache.NewInformer(
claimSource,
&v1.PersistentVolumeClaim{},
controller.resyncPeriod,
claimHandler,
)
}
2、PV
if controller.volumeInformer != nil {
controller.volumeInformer.AddEventHandlerWithResyncPeriod(volumeHandler, controller.resyncPeriod)
controller.volumes, controller.volumeController =
controller.volumeInformer.GetStore(),
controller.volumeInformer.GetController()
} else {
controller.volumes, controller.volumeController =
cache.NewInformer(
volumeSource,
&v1.PersistentVolume{},
controller.resyncPeriod,
volumeHandler,
)
}
3、StorageClass
if controller.classInformer != nil {
// no resource event handler needed for StorageClasses
controller.classes, controller.classController =
controller.classInformer.GetStore(),
controller.classInformer.GetController()
} else {
controller.classes, controller.classController = cache.NewInformer(
classSource,
versionedClassType,
controller.resyncPeriod,
classHandler,
)
}
通过cache.NewInformer的方法构造,入参是ListWatch结构体和ResourceEventHandlerFuncs函数等,返回值是Store和Controller。
通过以上各个部分的构造,最后返回一个具体的ProvisionController对象。
3.3. ProvisionController.Run方法
ProvisionController的Run方法是以常驻进程的方式运行,函数内部再运行其他的controller。
3.3.1. prometheus数据收集
// Run starts all of this controller's control loops
func (ctrl *ProvisionController) Run(stopCh <-chan struct{}) {
run := func(stopCh <-chan struct{}) {
...
if ctrl.metricsPort > 0 {
prometheus.MustRegister([]prometheus.Collector{
metrics.PersistentVolumeClaimProvisionTotal,
metrics.PersistentVolumeClaimProvisionFailedTotal,
metrics.PersistentVolumeClaimProvisionDurationSeconds,
metrics.PersistentVolumeDeleteTotal,
metrics.PersistentVolumeDeleteFailedTotal,
metrics.PersistentVolumeDeleteDurationSeconds,
}...)
http.Handle(ctrl.metricsPath, promhttp.Handler())
address := net.JoinHostPort(ctrl.metricsAddress, strconv.FormatInt(int64(ctrl.metricsPort), 10))
glog.Infof("Starting metrics server at %s\n", address)
go wait.Forever(func() {
err := http.ListenAndServe(address, nil)
if err != nil {
glog.Errorf("Failed to listen on %s: %v", address, err)
}
}, 5*time.Second)
}
...
}
3.3.2. Controller.Run
// If a SharedInformer has been passed in, this controller should not
// call Run again
if ctrl.claimInformer == nil {
go ctrl.claimController.Run(stopCh)
}
if ctrl.volumeInformer == nil {
go ctrl.volumeController.Run(stopCh)
}
if ctrl.classInformer == nil {
go ctrl.classController.Run(stopCh)
}
运行消息通知器Informer。
3.3.3. Worker
for i := 0; i < ctrl.threadiness; i++ {
go wait.Until(ctrl.runClaimWorker, time.Second, stopCh)
go wait.Until(ctrl.runVolumeWorker, time.Second, stopCh)
}
runClaimWorker和runVolumeWorker分别为PVC和PV的worker,这两个的具体执行体分别是processNextClaimWorkItem和processNextVolumeWorkItem。
执行流程如下:
PVC的函数调用流程
runClaimWorker→processNextClaimWorkItem→syncClaimHandler→syncClaim→provisionClaimOperation
PV的函数调用流程
runVolumeWorker→processNextVolumeWorkItem→syncVolumeHandler→syncVolume→deleteVolumeOperation
可见最后执行的函数分别是provisionClaimOperation和deleteVolumeOperation。
3.4. Operation
3.4.1. provisionClaimOperation
1、provisionClaimOperation入参是PVC,通过PVC获得PV对象,并判断PV对象是否存在,如果存在则退出后续操作。
// provisionClaimOperation attempts to provision a volume for the given claim.
// Returns error, which indicates whether provisioning should be retried
// (requeue the claim) or not
func (ctrl *ProvisionController) provisionClaimOperation(claim *v1.PersistentVolumeClaim) error {
// Most code here is identical to that found in controller.go of kube's PV controller...
claimClass := helper.GetPersistentVolumeClaimClass(claim)
operation := fmt.Sprintf("provision %q class %q", claimToClaimKey(claim), claimClass)
glog.Infof(logOperation(operation, "started"))
// A previous doProvisionClaim may just have finished while we were waiting for
// the locks. Check that PV (with deterministic name) hasn't been provisioned
// yet.
pvName := ctrl.getProvisionedVolumeNameForClaim(claim)
volume, err := ctrl.client.CoreV1().PersistentVolumes().Get(pvName, metav1.GetOptions{})
if err == nil && volume != nil {
// Volume has been already provisioned, nothing to do.
glog.Infof(logOperation(operation, "persistentvolume %q already exists, skipping", pvName))
return nil
}
...
}
2、获取StorageClass对象中的Provisioner和ReclaimPolicy参数,如果provisionerName和StorageClass对象中的provisioner字段不一致则报错并退出执行。
provisioner, parameters, err := ctrl.getStorageClassFields(claimClass)
if err != nil {
glog.Errorf(logOperation(operation, "error getting claim's StorageClass's fields: %v", err))
return nil
}
if provisioner != ctrl.provisionerName {
// class.Provisioner has either changed since shouldProvision() or
// annDynamicallyProvisioned contains different provisioner than
// class.Provisioner.
glog.Errorf(logOperation(operation, "unknown provisioner %q requested in claim's StorageClass", provisioner))
return nil
}
// Check if this provisioner can provision this claim.
if err = ctrl.canProvision(claim); err != nil {
ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, "ProvisioningFailed", err.Error())
glog.Errorf(logOperation(operation, "failed to provision volume: %v", err))
return nil
}
reclaimPolicy := v1.PersistentVolumeReclaimDelete
if ctrl.kubeVersion.AtLeast(utilversion.MustParseSemantic("v1.8.0")) {
reclaimPolicy, err = ctrl.fetchReclaimPolicy(claimClass)
if err != nil {
return err
}
}
3、执行具体的provisioner.Provision方法,构建PV对象,例如本文中的provisioner是nfs-provisioner。
options := VolumeOptions{
PersistentVolumeReclaimPolicy: reclaimPolicy,
PVName: pvName,
PVC: claim,
MountOptions: mountOptions,
Parameters: parameters,
SelectedNode: selectedNode,
AllowedTopologies: allowedTopologies,
}
ctrl.eventRecorder.Event(claim, v1.EventTypeNormal, "Provisioning", fmt.Sprintf("External provisioner is provisioning volume for claim %q", claimToClaimKey(claim)))
volume, err = ctrl.provisioner.Provision(options)
if err != nil {
if ierr, ok := err.(*IgnoredError); ok {
// Provision ignored, do nothing and hope another provisioner will provision it.
glog.Infof(logOperation(operation, "volume provision ignored: %v", ierr))
return nil
}
err = fmt.Errorf("failed to provision volume with StorageClass %q: %v", claimClass, err)
ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, "ProvisioningFailed", err.Error())
return err
}
4、创建k8s的PV对象。
// Try to create the PV object several times
for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ {
glog.Infof(logOperation(operation, "trying to save persistentvvolume %q", volume.Name))
if _, err = ctrl.client.CoreV1().PersistentVolumes().Create(volume); err == nil || apierrs.IsAlreadyExists(err) {
// Save succeeded.
if err != nil {
glog.Infof(logOperation(operation, "persistentvolume %q already exists, reusing", volume.Name))
err = nil
} else {
glog.Infof(logOperation(operation, "persistentvolume %q saved", volume.Name))
}
break
}
// Save failed, try again after a while.
glog.Infof(logOperation(operation, "failed to save persistentvolume %q: %v", volume.Name, err))
time.Sleep(ctrl.createProvisionedPVInterval)
}
5、创建PV失败,清理存储资源。
if err != nil {
// Save failed. Now we have a storage asset outside of Kubernetes,
// but we don't have appropriate PV object for it.
// Emit some event here and try to delete the storage asset several
// times.
...
for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ {
if err = ctrl.provisioner.Delete(volume); err == nil {
// Delete succeeded
glog.Infof(logOperation(operation, "cleaning volume %q succeeded", volume.Name))
break
}
// Delete failed, try again after a while.
glog.Infof(logOperation(operation, "failed to clean volume %q: %v", volume.Name, err))
time.Sleep(ctrl.createProvisionedPVInterval)
}
if err != nil {
// Delete failed several times. There is an orphaned volume and there
// is nothing we can do about it.
strerr := fmt.Sprintf("Error cleaning provisioned volume for claim %s: %v. Please delete manually.", claimToClaimKey(claim), err)
glog.Error(logOperation(operation, strerr))
ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, "ProvisioningCleanupFailed", strerr)
}
}
如果创建成功,则打印成功的日志,并返回nil。
3.4.2. deleteVolumeOperation
1、deleteVolumeOperation入参是PV,先获得PV对象,并判断是否需要删除。
// deleteVolumeOperation attempts to delete the volume backing the given
// volume. Returns error, which indicates whether deletion should be retried
// (requeue the volume) or not
func (ctrl *ProvisionController) deleteVolumeOperation(volume *v1.PersistentVolume) error {
...
// This method may have been waiting for a volume lock for some time.
// Our check does not have to be as sophisticated as PV controller's, we can
// trust that the PV controller has set the PV to Released/Failed and it's
// ours to delete
newVolume, err := ctrl.client.CoreV1().PersistentVolumes().Get(volume.Name, metav1.GetOptions{})
if err != nil {
return nil
}
if !ctrl.shouldDelete(newVolume) {
glog.Infof(logOperation(operation, "persistentvolume no longer needs deletion, skipping"))
return nil
}
...
}
2、调用具体的provisioner的Delete方法,例如,如果是nfs-provisioner,则是调用nfs-provisioner的Delete方法。
err = ctrl.provisioner.Delete(volume)
if err != nil {
if ierr, ok := err.(*IgnoredError); ok {
// Delete ignored, do nothing and hope another provisioner will delete it.
glog.Infof(logOperation(operation, "volume deletion ignored: %v", ierr))
return nil
}
// Delete failed, emit an event.
glog.Errorf(logOperation(operation, "volume deletion failed: %v", err))
ctrl.eventRecorder.Event(volume, v1.EventTypeWarning, "VolumeFailedDelete", err.Error())
return err
}
3、删除k8s中的PV对象。
// Delete the volume
if err = ctrl.client.CoreV1().PersistentVolumes().Delete(volume.Name, nil); err != nil {
// Oops, could not delete the volume and therefore the controller will
// try to delete the volume again on next update.
glog.Infof(logOperation(operation, "failed to delete persistentvolume: %v", err))
return err
}
4. 总结
Provisioner接口包含Provision和Delete两个方法,自定义的provisioner需要实现这两个方法,这两个方法只是处理了跟存储类型相关的事项,并没有针对PV、PVC对象的增删等操作。Provision方法主要用来构造PV对象,不同类型的Provisioner的,一般是PersistentVolumeSource类型和参数不同,例如nfs-provisioner对应的PersistentVolumeSource为NFS,并且需要传入NFS相关的参数:Server,Path等。Delete方法主要针对对应的存储类型,做数据存档(备份)或删除的处理。StorageClass对象需要单独创建,用来指定具体的provisioner来执行相关逻辑。provisionClaimOperation和deleteVolumeOperation具体执行了k8s中PV对象的创建和删除操作,同时调用了具体provisioner的Provision和Delete两个方法来对存储数据做处理。
参考文章
- https://github.com/kubernetes-incubator/external-storage/tree/master/docs/demo/hostpath-provisioner
- https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client
- https://github.com/kubernetes-incubator/external-storage/blob/master/lib/controller/controller.go
- https://github.com/kubernetes-incubator/external-storage/blob/master/lib/controller/volume.go
9.4 - k8s社区开发指南
1. 社区说明
1.1. Community membership
| Role | Responsibilities | Requirements | Defined by |
|---|---|---|---|
| Member | Active contributor in the community | Sponsored by 2 reviewers and multiple contributions to the project | Kubernetes GitHub org member |
| Reviewer | Review contributions from other members | History of review and authorship in a subproject | OWNERS file reviewer entry |
| Approver | Contributions acceptance approval | Highly experienced active reviewer and contributor to a subproject | OWNERS file approver entry |
| Subproject owner | Set direction and priorities for a subproject | Demonstrated responsibility and excellent technical judgement for the subproject | sigs.yaml subproject OWNERS file owners entry |
1.2. 社区活动日历
Community Calendar | Kubernetes Contributors
1.3. 加入k8s slack
点击 https://communityinviter.com/apps/kubernetes/community
1.4. 特别兴趣小组(SIG)
列表: https://github.com/kubernetes/community/blob/master/sig-list.md
2. 编译k8s仓库
参考:
- Building Kubernetes
- https://github.com/kubernetes/community/blob/master/contributors/devel/development.md#building-kubernetes
2.1. 编译二进制
2.1.1. 基于docker构建容器编译。
该方式为官方镜像及二进制文件的构建方式。
构建镜像(大小:5.97GB)为: kube-build:build-8faa8d3cb7-5-v1.27.0-go1.20.6-bullseye.0
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
build/run.sh make # 构建全部
#指定模块构建
build/run.sh make kubeadm
输出如下:
# build/run.sh make
+++ [0804 18:39:11] Verifying Prerequisites....
+++ [0804 18:39:16] Building Docker image kube-build:build-8faa8d3cb7-5-v1.27.0-go1.20.6-bullseye.0
+++ [0804 18:40:49] Creating data container kube-build-data-8faa8d3cb7-5-v1.27.0-go1.20.6-bullseye.0
+++ [0804 18:40:50] Syncing sources to container
+++ [0804 18:40:58] Output from this container will be rsynced out upon completion. Set KUBE_RUN_COPY_OUTPUT=n to disable.
+++ [0804 18:40:58] Running build command...
go: downloading go.uber.org/automaxprocs v1.5.2
+++ [0804 18:41:04] Setting GOMAXPROCS: 8
Go version: go version go1.20.6 linux/amd64
+++ [0804 18:41:04] Building go targets for linux/amd64
k8s.io/kubernetes/cmd/kube-proxy (static)
k8s.io/kubernetes/cmd/kube-apiserver (static)
k8s.io/kubernetes/cmd/kube-controller-manager (static)
k8s.io/kubernetes/cmd/kubelet (non-static)
k8s.io/kubernetes/cmd/kubeadm (static)
k8s.io/kubernetes/cmd/kube-scheduler (static)
k8s.io/component-base/logs/kube-log-runner (static)
k8s.io/kube-aggregator (static)
k8s.io/apiextensions-apiserver (static)
k8s.io/kubernetes/cluster/gce/gci/mounter (non-static)
k8s.io/kubernetes/cmd/kubectl (static)
k8s.io/kubernetes/cmd/kubectl-convert (static)
github.com/onsi/ginkgo/v2/ginkgo (non-static)
k8s.io/kubernetes/test/e2e/e2e.test (test)
k8s.io/kubernetes/test/conformance/image/go-runner (non-static)
k8s.io/kubernetes/cmd/kubemark (static)
github.com/onsi/ginkgo/v2/ginkgo (non-static)
k8s.io/kubernetes/test/e2e_node/e2e_node.test (test)
Env for linux/amd64: GOOS=linux GOARCH=amd64 GOROOT=/usr/local/go CGO_ENABLED= CC=
Coverage is disabled.
Coverage is disabled.
+++ [0804 18:48:17] Placing binaries
+++ [0804 18:48:25] Syncing out of container
产物文件在_output目录上。
kubernetes/_output# tree
.
|-- dockerized
| |-- bin
| | `-- linux
| | `-- amd64
| | |-- apiextensions-apiserver
| | |-- e2e_node.test
| | |-- e2e.test
| | |-- ginkgo
| | |-- go-runner
| | |-- kubeadm
| | |-- kube-aggregator
| | |-- kube-apiserver
| | |-- kube-controller-manager
| | |-- kubectl
| | |-- kubectl-convert
| | |-- kubelet
| | |-- kube-log-runner
| | |-- kubemark
| | |-- kube-proxy
| | |-- kube-scheduler
| | |-- mounter
| | `-- ncpu
| `-- go
`-- images
`-- kube-build:build-8faa8d3cb7-5-v1.27.0-go1.20.6-bullseye.0
|-- Dockerfile
|-- localtime
|-- rsyncd.password
`-- rsyncd.sh
2.1.2. 基于构建机环境编译
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
# 构建全部二进制
make
# 构建指定二进制
make WHAT=cmd/kubeadm
输出如下:
# make WHAT=cmd/kubeadm
go version go1.20.6 linux/amd64
+++ [0804 19:30:55] Setting GOMAXPROCS: 8
+++ [0804 19:30:56] Building go targets for linux/amd64
k8s.io/kubernetes/cmd/kubeadm (static)
2.2. 编译镜像
git clone https://github.com/kubernetes/kubernetes
cd kubernetes
make quick-release
3. 如何给k8s提交PR
参考:
-
https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md
-
https://github.com/kubernetes/community/blob/master/contributors/guide/first-contribution.md
参考:
10 - 问题排查
10.1 - 节点问题
10.1.1 - keycreate permission denied
问题描述
write /proc/self/attr/keycreate: permission denied
具体报错:
kuberuntime_manager.go:758] createPodSandbox for pod "ecc-hostpath-provisioner-8jbhf_kube-system(b8050fd3-4ffe-11eb-a82e-c6090b53405b)" failed: rpc error: code = Unknown desc = failed to start sandbox container for pod "ecc-hostpath-provisioner-8jbhf": Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \"write /proc/self/attr/keycreate: permission denied\"": unknown
解决办法
SELINUX未设置成disabled
# 将SELINUX设置成disabled
setenforce 0 # 临时生效
# 永久生效,但需重启,配合上述命令可以不用立即重启
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config
# 查看SELinux状态
$ /usr/sbin/sestatus -v
SELinux status: disabled
$ getenforce
Disabled
10.1.2 - Cgroup不支持pid资源
问题描述
机器内核版本较低,kubelet启动异常,报错如下:
Failed to start ContainerManager failed to initialize top level QOS containers: failed to update top level Burstable QOS cgroup : failed to set supported cgroup subsystems for cgroup [kubepods burstable]: Failed to find subsystem mount for required subsystem: pids
原因分析
低版本内核的cgroup不支持pids资源的功能,
cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 5 6 1
cpu 2 76 1
cpuacct 2 76 1
memory 4 76 1
devices 10 76 1
freezer 7 6 1
net_cls 3 6 1
blkio 8 76 1
perf_event 9 6 1
hugetlb 6 6 1
正常机器的cgroup
root@host:~# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 5 17 1
cpu 7 80 1
cpuacct 7 80 1
memory 12 80 1
devices 10 80 1
freezer 2 17 1
net_cls 4 17 1
blkio 8 80 1
perf_event 6 17 1
hugetlb 11 17 1
pids 3 80 1 # 此处支持pids资源
oom 9 1 1
解决方案
1、升级内核版本,使得cgroup支持pids资源。
或者
2、将kubelet的启动参数添加 SupportPodPidsLimit=false,SupportNodePidsLimit=false
vi /etc/systemd/system/kubelet.service
# 添加 kubelet 启动参数
--feature-gates=... ,SupportPodPidsLimit=false,SupportNodePidsLimit=false \
systemctl daemon-reload && systemctl restart kubelet.service
文档参考:
10.1.3 - Cgroup子系统无法挂载
问题描述
内核版本: 5.4.56-200.el7.x86_64
docker报错
May 13 16:54:26 8b26d7a8 dockerd[44352]: time="2021-05-13T16:54:26.565235530+08:00" level=warning msg="failed to load plugin io.containerd.snapshotter.v1.devmapper" error="devmapper not configured"
May 13 16:54:26 8b26d7a8 dockerd[44352]: time="2021-05-13T16:54:26.565525512+08:00" level=warning msg="could not use snapshotter devmapper in metadata plugin" error="devmapper not configured"
May 13 16:54:26 8b26d7a8 dockerd[44352]: time="2021-05-13T16:54:26.574734345+08:00" level=warning msg="Your kernel does not support CPU realtime scheduler"
May 13 16:54:26 8b26d7a8 dockerd[44352]: time="2021-05-13T16:54:26.574792864+08:00" level=warning msg="Your kernel does not support cgroup blkio weight"
May 13 16:54:26 8b26d7a8 dockerd[44352]: time="2021-05-13T16:54:26.574800326+08:00" level=warning msg="Your kernel does not support cgroup blkio weight_device"
kubelet报错

解决
cgroup问题解决:
1、curl https://pi-ops.oss-cn-hangzhou.aliyuncs.com/scripts/cgroupfs-mount.sh | bash
2、重启设备即可解决
10.1.4 - runc-v1.1.3-exec-failed
问题描述
当使用runc 1.1.3的版本时,如果执行systemctl daemon-reload后,通过exec进入容器则会触发以下错误,无法进入容器。
FATA[0000] execing command in container: Internal error occurred: error executing command in container: failed to exec in container: failed to start exec "33d05b4f71a2da69c8c77cc3f7e61451814eb150edd15d0a3153b57a862126d4": OCI runtime exec failed: exec failed: unable to start container process: open /dev/pts/0: operation not permitted: unknown
原因
这个是runc 1.1.3版本起存在的一个bug,runc1.1.2的版本不存在,社区在runc 1.1.4的版本中修复了这个bug。由于 runc v1.1.3 中不再添加 DeviceAllow=char-pts rwm 规则了,当执行 systemctl daemon-reload 后, 会导致重新应用 systemd 的规则,进而导致这条规则的缺失。
解决方案
升级runc到1.1.4的版本,并且所有通过runc1.1.3创建的pod都需要重建才能生效。
参考:
10.1.5 - increase the mlock limit
问题描述
容器启动报错:increase the mlock limit,原因是ulimit mlock值比较小,需要将ulimit值调大。
报错如下:
runtime: mlock of signal stack failed: 12
runtime: increase the mlock limit (ulimit -l) or
runtime: update your kernel to 5.3.15+, 5.4.2+, or 5.5+
fatal error: mlock failed
runtime stack:
runtime.throw(0x1a7729f, 0xc)
/usr/local/go/src/runtime/panic.go:1112 +0x72
runtime.mlockGsignal(0xc000702300)
/usr/local/go/src/runtime/os_linux_x86.go:72 +0x107
runtime.mpreinit(0xc000588380)
/usr/local/go/src/runtime/os_linux.go:341 +0x78
runtime.mcommoninit(0xc000588380)
/usr/local/go/src/runtime/proc.go:630 +0x108
runtime.allocm(0xc000072000, 0x1adcb70, 0x0)
/usr/local/go/src/runtime/proc.go:1390 +0x14e
runtime.newm(0x1adcb70, 0xc000072000)
/usr/local/go/src/runtime/proc.go:1704 +0x39
runtime.startm(0x0, 0xc000267e01)
/usr/local/go/src/runtime/proc.go:1869 +0x12a
runtime.wakep(...)
/usr/local/go/src/runtime/proc.go:1953
runtime.resetspinning()
/usr/local/go/src/runtime/proc.go:2415 +0x93
runtime.schedule()
/usr/local/go/src/runtime/proc.go:2527 +0x2de
runtime.mstart1()
/usr/local/go/src/runtime/proc.go:1104 +0x8e
runtime.mstart()
/usr/local/go/src/runtime/proc.go:1062 +0x6e
goroutine 1 [runnable, locked to thread]:
github.com/xdg/stringprep.init()
/root/go/pkg/mod/github.com/xdg/stringprep@v1.0.3/tables.go:443 +0x19087
goroutine 43 [select]:
go.opencensus.io/stats/view.(*worker).start(0xc00067e800)
/root/go/pkg/mod/go.opencensus.io@v0.23.0/stats/view/worker.go:276 +0x100
created by go.opencensus.io/stats/view.init.0
/root/go/pkg/mod/go.opencensus.io@v0.23.0/stats/view/worker.go:34 +0x68
原因
宿主机的ulimit值比较小,需要将内存的ulimit值调大。
$ ulimit -l
64
解决方案
vi /lib/systemd/system/containerd.service。在containerd.service文件中增加LimitMEMLOCK=infinity 参数。
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target
[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitMEMLOCK=infinity
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
# Comment TasksMax if your systemd version does not supports it.
# Only systemd 226 and above support this version.
TasksMax=infinity
OOMScoreAdjust=-999
[Install]
WantedBy=multi-user.target
重启containerd
systemctl daemon-reload
systemctl restart containerd
systemctl status containerd
10.1.6 - Kubelet初始化QOS失败
问题描述
现象:kubelet启动不起来,报错如下:
kubelet.go:1703] "Failed to start ContainerManager" err="failed to initialize top level QOS containers: error validating root container [kubelet kubepods] : cgroup [\"kubelet\" \"kubepods\"] has some missing paths: /sys/fs/cgroup/systemd/kubelet.slice/kubelet-kubepods.slice"
解决方案
systemctl stop kubepods.slice
systemctl restart kubelet
参考:
10.2 - Pod驱逐
问题描述
节点Pod被驱逐
原因
1. 查看节点和该节点pod状态
查看节点状态为Ready,查看该节点的所有pod,发现存在被驱逐的pod和nvidia-device-plugin为pending
root@host:~$ kgpoallowide |grep 192.168.1.1
department-56 173e397c-ea35-4aac-85d8-07106e55d7b7 0/1 Evicted 0 52d <none> 192.168.1.1 <none>
kube-system nvidia-device-plugin-daemonset-d58d2 0/1 Pending 0 1s <none> 192.168.1.1 <none>
2. 查看对应节点kubelet的日志
0905 15:42:13.182280 23506 eviction_manager.go:142] Failed to admit pod rdma-device-plugin-daemonset-8nwb8_kube-system(acc28a85-cfb0-11e9-9729-6c92bf5e2432) - node has conditions: [DiskPressure]
I0905 15:42:14.827343 23506 kubelet.go:1836] SyncLoop (ADD, "api"): "nvidia-device-plugin-daemonset-88sm6_kube-system(adbd9227-cfb0-11e9-9729-6c92bf5e2432)"
W0905 15:42:14.827372 23506 eviction_manager.go:142] Failed to admit pod nvidia-device-plugin-daemonset-88sm6_kube-system(adbd9227-cfb0-11e9-9729-6c92bf5e2432) - node has conditions: [DiskPressure]
I0905 15:42:15.722378 23506 kubelet_node_status.go:607] Update capacity for nvidia.com/gpu-share to 0
I0905 15:42:16.692488 23506 kubelet.go:1852] SyncLoop (DELETE, "api"): "rdma-device-plugin-daemonset-8nwb8_kube-system(acc28a85-cfb0-11e9-9729-6c92bf5e2432)"
W0905 15:42:16.698445 23506 status_manager.go:489] Failed to delete status for pod "rdma-device-plugin-daemonset-8nwb8_kube-system(acc28a85-cfb0-11e9-9729-6c92bf5e2432)": pod "rdma-device-plugin-daemonset-8nwb8" not found
I0905 15:42:16.698490 23506 kubelet.go:1846] SyncLoop (REMOVE, "api"): "rdma-device-plugin-daemonset-8nwb8_kube-system(acc28a85-cfb0-11e9-9729-6c92bf5e2432)"
I0905 15:42:16.699267 23506 kubelet.go:2040] Failed to delete pod "rdma-device-plugin-daemonset-8nwb8_kube-system(acc28a85-cfb0-11e9-9729-6c92bf5e2432)", err: pod not found
W0905 15:42:16.777355 23506 eviction_manager.go:332] eviction manager: attempting to reclaim nodefs
I0905 15:42:16.777384 23506 eviction_manager.go:346] eviction manager: must evict pod(s) to reclaim nodefs
E0905 15:42:16.777390 23506 eviction_manager.go:357] eviction manager: eviction thresholds have been met, but no pods are active to evict
存在关于pod驱逐相关的日志,驱逐的原因为node has conditions: [DiskPressure]。
3. 查看磁盘相关信息
[root@host /]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 126G 0 126G 0% /dev
tmpfs 126G 0 126G 0% /dev/shm
tmpfs 126G 27M 126G 1% /run
tmpfs 126G 0 126G 0% /sys/fs/cgroup
/dev/sda1 20G 19G 0 100% / # 根目录磁盘满
/dev/nvme1n1 3.0T 191G 2.8T 7% /data2
/dev/nvme0n1 3.0T 1.3T 1.7T 44% /data1
/dev/sda4 182G 95G 87G 53% /data
/dev/sda3 20G 3.8G 15G 20% /usr/local
tmpfs 26G 0 26G 0% /run/user/0
发现根目录的磁盘盘,接着查看哪些文件占用磁盘。
[root@host ~/kata]# du -sh ./*
1.0M ./log
944K ./netlink
6.6G ./kernel3
/var/log/下存在7G 的日志。清理相关日志和无用文件后,根目录恢复空间。
[root@host /data]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 126G 0 126G 0% /dev
tmpfs 126G 0 126G 0% /dev/shm
tmpfs 126G 27M 126G 1% /run
tmpfs 126G 0 126G 0% /sys/fs/cgroup
/dev/sda1 20G 5.8G 13G 32% / # 根目录正常
/dev/nvme1n1 3.0T 191G 2.8T 7% /data2
查看节点pod状态,相关plugin的pod恢复正常。
root@host:~$ kgpoallowide |grep 192.168.1.1
kube-system nvidia-device-plugin-daemonset-h4pjc 1/1 Running 0 16m 192.168.1.1 192.168.1.1 <none>
kube-system rdma-device-plugin-daemonset-xlkbv 1/1 Running 0 16m 192.168.1.1 192.168.1.1 <none>
4. 查看kubelet配置
查看kubelet关于pod驱逐相关的参数配置,可见节点kubelet开启了驱逐机制,正常情况下该配置应该是关闭的。
ExecStart=/usr/local/bin/kubelet \
...
--eviction-hard=nodefs.available<1% \
解决方案
总结以上原因为,kubelet开启了pod驱逐的机制,根目录的磁盘达到100%,pod被驱逐,且无法再正常创建在该节点。
解决方案如下:
1、关闭kubelet的驱逐机制。
2、清除根目录的文件,恢复根目录空间,并后续增加根目录的磁盘监控。
10.3 - 镜像拉取失败问题
常见镜像拉取问题排查
1. Pod状态为ErrImagePull或ImagePullBackOff
docker-hub-75d4dfb984-5hggg 0/1 ImagePullBackOff 0 14m 192.168.1.30 <node ip>
docker-hub-75d4dfb984-9r57b 0/1 ErrImagePull 0 53s 192.168.0.42 <node ip>
- ErrImagePull:表示pod已经调度到node节点,kubelet调用docker去拉取镜像失败。
- ImagePullBackOff:表示kubelet拉取镜像失败后,不断重试去拉取仍然失败。
2. 查看pod的事件
通过kubectl describe pod 命令查看pod事件,该事件的报错信息在kubelet或docker的日志中也会查看到。
2.1. http: server gave HTTP response to HTTPS client
如果遇到以下报错,尝试将该镜像仓库添加到docker可信任的镜像仓库配置中。
Error getting v2 registry: Get https://docker.com:8080/v2/: http: server gave HTTP response to HTTPS client"
具体操作是修改/etc/docker/daemon.json的insecure-registries参数
#cat /etc/docker/daemon.json
{
...
"insecure-registries": [
...
"docker.com:8080"
],
...
}
2.2. no basic auth credentials
如果遇到no basic auth credentials报错,说明kubelet调用docker接口去拉取镜像时,镜像仓库的认证信息失败。
Normal BackOff 18s kubelet, 192.168.1.1 Back-off pulling image "docker.com:8080/public/2048:latest"
Warning Failed 18s kubelet, 192.168.1.1 Error: ImagePullBackOff
Normal Pulling 5s (x2 over 18s) kubelet, 192.168.1.1 Pulling image "docker.com:8080/public/2048:latest"
Warning Failed 5s (x2 over 18s) kubelet, 192.168.1.1 Failed to pull image "docker.com:8080/public/2048:latest": rpc error: code = Unknown desc = Error response from daemon: Get http://docker.com:8080/v2/public/2048/manifests/latest: no basic auth credentials
Warning Failed 5s (x2 over 18s) kubelet, 192.168.1.1 Error: ErrImagePull
具体操作,在拉取镜像失败的节点上登录该镜像仓库,认证信息会更新到 $HOME/.docker/config.json文件中。将该文件拷贝到/var/lib/kubelet/config.json中。
10.4 - PVC Terminating
问题描述
pvc terminating
pvc在删除时,卡在terminating中。
解决方法
kubectl patch pvc {PVC_NAME} -p '{"metadata":{"finalizers":null}}'
10.5 - ConfigMap多行格式
问题
configmap出现多行文本无法正常显示换行格式,而是以\n连接文本,查看和编辑时可读性很差。
apiVersion: v1
data:
config.yaml: "# log options\nlog_level: \"info\"\nlog_output: \"stderr\"\ncert_file:
\"/etc/webhook/certs/cert.pem\"\nkey_file: \"/etc/webhook/certs/key.pem\"\nhttp_listen:
\":8080\"\nhttps_listen: \":8443\"\ningress_publish_service: \nenable_profiling:
true\nkubernetes:\n kubeconfig: \"\"\n resync_interval: \"6h\"\n app_namespaces:\n
\ - \"*\"\n namespace_selector:\n - \"\"\n election_id: \"ingress-apisix-leader\"\n
\ ingress_class: \"ph-apisix\"\n ingress_version: \"networking/v1\"\n watch_endpointslices:
false\n apisix_route_version: \"apisix.apache.org/v2beta3\"\n enable_gateway_api:
false\napisix:\n default_cluster_base_url: http://apisix-admin.apisix.svc.cluster.local:9180/apisix/admin\n
\ default_cluster_admin_key: \"edd1c9f034335f136f87ad84b625c8f1\"\n default_cluster_name:
\"default\""
kind: ConfigMap
解决方案
如果要保持多行输入和输出的格式,则需要符合以下情况:
- 文本不要以空格结尾
- 不要换行前再带个空格
- 不要在文本中添加不可见特殊字符
将文本拷贝并格式化yaml文本。可使用在线格式化工具:YAML在线格式化。
将格式化的文本拷贝到configmap文件,并检查上述三个问题。一般是因以空格结尾导致,搜索空格并去除行末的空格。
apiVersion: v1
data:
config.yaml: |-
# log options
log_level: "info"
log_output: "stderr"
cert_file: "/etc/webhook/certs/cert.pem"
key_file: "/etc/webhook/certs/key.pem"
http_listen: ":8080"
https_listen: ":8443"
ingress_publish_service:
enable_profiling: true
参考:
11 - Runtime
11.1 - Runc和Containerd概述
本文主要分析OCI,CRI,runc,containerd,cri-containerd,dockershim等组件说明及调用关系。
1. 概述
各个组件调用关系图如下:

图片来源:https://www.jianshu.com/p/62e71584d1cb
2. OCI(Open Container Initiative)
OCI(Open Container Initiative)即开放的容器运行时规范,目的在于定义一个容器运行时及镜像的相关标准和规范,其中包括
- runtime-spec:容器的生命周期管理,具体参考runtime-spec。
- image-spec:镜像的生命周期管理,具体参考image-spec。
实现OCI标准的容器运行时有runc,kata等。
3. RunC
runc(run container)是一个基于OCI标准实现的一个轻量级容器运行工具,用来创建和运行容器。而Containerd是用来维持通过runc创建的容器的运行状态。即runc用来创建和运行容器,containerd作为常驻进程用来管理容器。
runc包含libcontainer,包括对namespace和cgroup的调用操作。
命令参数:
To start a new instance of a container:
# runc run [ -b bundle ] <container-id>
USAGE:
runc [global options] command [command options] [arguments...]
COMMANDS:
checkpoint checkpoint a running container
create create a container
delete delete any resources held by the container often used with detached container
events display container events such as OOM notifications, cpu, memory, and IO usage statistics
exec execute new process inside the container
init initialize the namespaces and launch the process (do not call it outside of runc)
kill kill sends the specified signal (default: SIGTERM) to the container's init process
list lists containers started by runc with the given root
pause pause suspends all processes inside the container
ps ps displays the processes running inside a container
restore restore a container from a previous checkpoint
resume resumes all processes that have been previously paused
run create and run a container
spec create a new specification file
start executes the user defined process in a created container
state output the state of a container
update update container resource constraints
help, h Shows a list of commands or help for one command
4. Containerd
containerd(container daemon)是一个daemon进程用来管理和运行容器,可以用来拉取/推送镜像和管理容器的存储和网络。其中可以调用runc来创建和运行容器。
4.1. containerd的架构图

4.2. docker与containerd、runc的关系图

更具体的调用逻辑:

5. CRI(Container Runtime Interface )
CRI即容器运行时接口,主要用来定义k8s与容器运行时的API调用,kubelet通过CRI来调用容器运行时,只要实现了CRI接口的容器运行时就可以对接到k8s的kubelet组件。

5.1. docker与k8s调用containerd的关系图

5.2. cri-api
5.2.1. runtime service
// Runtime service defines the public APIs for remote container runtimes
service RuntimeService {
// Version returns the runtime name, runtime version, and runtime API version.
rpc Version(VersionRequest) returns (VersionResponse) {}
// RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure
// the sandbox is in the ready state on success.
rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
// StopPodSandbox stops any running process that is part of the sandbox and
// reclaims network resources (e.g., IP addresses) allocated to the sandbox.
// If there are any running containers in the sandbox, they must be forcibly
// terminated.
// This call is idempotent, and must not return an error if all relevant
// resources have already been reclaimed. kubelet will call StopPodSandbox
// at least once before calling RemovePodSandbox. It will also attempt to
// reclaim resources eagerly, as soon as a sandbox is not needed. Hence,
// multiple StopPodSandbox calls are expected.
rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
// RemovePodSandbox removes the sandbox. If there are any running containers
// in the sandbox, they must be forcibly terminated and removed.
// This call is idempotent, and must not return an error if the sandbox has
// already been removed.
rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
// PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not
// present, returns an error.
rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
// ListPodSandbox returns a list of PodSandboxes.
rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}
// CreateContainer creates a new container in specified PodSandbox
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
// StartContainer starts the container.
rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
// StopContainer stops a running container with a grace period (i.e., timeout).
// This call is idempotent, and must not return an error if the container has
// already been stopped.
// The runtime must forcibly kill the container after the grace period is
// reached.
rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
// RemoveContainer removes the container. If the container is running, the
// container must be forcibly removed.
// This call is idempotent, and must not return an error if the container has
// already been removed.
rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
// ListContainers lists all containers by filters.
rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
// ContainerStatus returns status of the container. If the container is not
// present, returns an error.
rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
// UpdateContainerResources updates ContainerConfig of the container.
rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {}
// ReopenContainerLog asks runtime to reopen the stdout/stderr log file
// for the container. This is often called after the log file has been
// rotated. If the container is not running, container runtime can choose
// to either create a new log file and return nil, or return an error.
// Once it returns error, new container log file MUST NOT be created.
rpc ReopenContainerLog(ReopenContainerLogRequest) returns (ReopenContainerLogResponse) {}
// ExecSync runs a command in a container synchronously.
rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {}
// Exec prepares a streaming endpoint to execute a command in the container.
rpc Exec(ExecRequest) returns (ExecResponse) {}
// Attach prepares a streaming endpoint to attach to a running container.
rpc Attach(AttachRequest) returns (AttachResponse) {}
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {}
// ContainerStats returns stats of the container. If the container does not
// exist, the call returns an error.
rpc ContainerStats(ContainerStatsRequest) returns (ContainerStatsResponse) {}
// ListContainerStats returns stats of all running containers.
rpc ListContainerStats(ListContainerStatsRequest) returns (ListContainerStatsResponse) {}
// UpdateRuntimeConfig updates the runtime configuration based on the given request.
rpc UpdateRuntimeConfig(UpdateRuntimeConfigRequest) returns (UpdateRuntimeConfigResponse) {}
// Status returns the status of the runtime.
rpc Status(StatusRequest) returns (StatusResponse) {}
}
5.2.2. image service
// ImageService defines the public APIs for managing images.
service ImageService {
// ListImages lists existing images.
rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {}
// ImageStatus returns the status of the image. If the image is not
// present, returns a response with ImageStatusResponse.Image set to
// nil.
rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {}
// PullImage pulls an image with authentication config.
rpc PullImage(PullImageRequest) returns (PullImageResponse) {}
// RemoveImage removes the image.
// This call is idempotent, and must not return an error if the image has
// already been removed.
rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {}
// ImageFSInfo returns information of the filesystem that is used to store images.
rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {}
}
5.3. cri-containerd

5.3.1. CRI Plugin调用流程
- kubelet调用CRI插件,通过CRI Runtime Service接口创建pod
- cri通过CNI接口创建和配置pod的network namespace
- cri调用containerd创建sandbox container(pause container )并将容器放入pod的cgroup和namespace中
- kubelet调用CRI插件,通过image service接口拉取镜像,接着通过containerd来拉取镜像
- kubelet调用CRI插件,通过runtime service接口运行拉取下来的镜像服务,最后通过containerd来运行业务容器,并将容器放入pod的cgroup和namespace中。
具体参考:https://github.com/containerd/cri/blob/release/1.4/docs/architecture.md
5.3.2. k8s对runtime调用的演进
由原来通过dockershim调用docker再调用containerd,直接变成通过cri-containerd调用containerd,从而减少了一层docker调用逻辑。

具体参考:https://github.com/containerd/cri/blob/release/1.4/docs/proposal.md
5.4. Dockershim
在旧版本的k8s中,由于docker没有实现CRI接口,因此增加一个Dockershim来实现k8s对docker的调用。(shim:垫片,一般用来表示对第三方组件API调用的适配插件,例如k8s使用Dockershim来实现对docker接口的适配调用)
5.5. CRI-O
cri-o与containerd类似,用来实现容器的管理,可替换containerd的使用。
参考:
- https://opencontainers.org/about/overview/
- https://github.com/opencontainers/runtime-spec
- https://github.com/kubernetes/kubernetes/blob/242a97307b34076d5d8f5bbeb154fa4d97c9ef1d/docs/devel/container-runtime-interface.md
- https://github.com/containerd/containerd/blob/main/docs/cri/architecture.md
- https://www.tutorialworks.com/difference-docker-containerd-runc-crio-oci/
- https://kubernetes.io/zh/docs/setup/production-environment/container-runtimes/
11.2 - Namespace
11.2.1 - Namespace介绍
1. Namespace简介
Namespace是内核的一个功能,用来给进程隔离一系列系统资源(视图隔离)。
2. 类别
| namespace类别 | 隔离资源 | 系统调用参数 | 内核版本 | Docker中的例子 |
|---|---|---|---|---|
| Mount namespace | 挂载点 | CLONE_NEWNS | 2.4.19 | 独立的挂载点 |
| UTS Namespace | hostname和domainname | CLONE_NEWUTS | 2.6.19 | 独立的hostname |
| IPC Namespace | System V IPC, POSIX message queues | CLONE_NEWIPC | 2.6.19 | |
| PID Namespace | 进程ID | CLONE_NEWPID | 2.6.24 | 容器进程PID为1 |
| Network Namespace | 网络设备,端口,网络栈 | CLONE_NEWNET | 2.6.24 | 独立的网络和端口 |
| User Namespace | 用户ID,group ID | CLONE_NEWUSER | 3.8 | 独立的用户ID |
3. Namespace API
| API | 说明 |
|---|---|
| clone() | 基于某namespace创建新进程,他们的子进程也包含在该namespace中 |
| unshare() | 将进程移出某个namespace |
| setns() | 将进程加入某个namespace |
4. namespace细分
4.1. Mount Namespace
Mount Namespace可以用了隔离各个进程的挂载点视图。不同的namespace中文件系统层次不一样,在其中调用mount和umount仅影响当前namespace。
4.2. Network Namespace
Network Namespace用来隔离网络设备,IP地址端口等网络栈。容器内可以绑定自己的端口,在宿主机建立网桥,就可以实现容器之间的通信。
ip netns 命令用来管理 network namespace。
# ip netns help
Usage: ip netns list
ip netns add NAME
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
ip netns identify [PID]
ip netns pids NAME
ip [-all] netns exec [NAME] cmd ...
ip netns monitor
ip netns list-id
示例:
模拟创建docker0及docker网络
1、创建lxcbr0,相当于docker0
# 添加一个网桥lxcbr0,相当于docker0
brctl addbr lxcbr0
brctl stp lxcbr0 off
ifconfig lxcbr0 192.168.10.1/24 up # 配置网桥IP地址
# 查看网桥
# ifconfig
lxcbr0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.10.1 netmask 255.255.255.0 broadcast 192.168.10.255
inet6 fe80::94cb:eaff:fe48:cdd5 prefixlen 64 scopeid 0x20<link>
ether 96:cb:ea:48:cd:d5 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5 bytes 426 (426.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
添加网络命名空间
# 添加网络命名空间ns1
ip netns add ns1
# 激活namespace中的loopback,即127.0.0.1
ip netns exec ns1 ip link set dev lo up
3、添加虚拟网卡
# 增加一个pair虚拟网卡,注意其中的veth类型,其中一个网卡要按进容器中
ip link add veth-ns1 type veth peer name lxcbr0.1
# 把 veth-ns1 按到namespace ns1中,这样容器中就会有一个新的网卡了
ip link set veth-ns1 netns ns1
4、修改容器内网卡为eth0,并分配IP
# 把容器里的 veth-ns1改名为 eth0 (容器外会冲突,容器内就不会了)
ip netns exec ns1 ip link set dev veth-ns1 name eth0
# 为容器中的网卡分配一个IP地址,并激活它
ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up
5、将lxcbr0.1添加上网桥
# 上面我们把veth-ns1这个网卡按到了容器中,然后我们要把lxcbr0.1添加上网桥上
brctl addif lxcbr0 lxcbr0.1
6、添加路由
# 为容器增加一个路由规则,让容器可以访问外面的网络
ip netns exec ns1 ip route add default via 192.168.10.1
7、添加nameserver
# 在/etc/netns下创建network namespce名称为ns1的目录,
# 然后为这个namespace设置resolv.conf,这样,容器内就可以访问域名了
mkdir -p /etc/netns/ns1
echo "nameserver 8.8.8.8" > /etc/netns/ns1/resolv.conf
8、查看网络空间内的网络配置
# 进入网络命名空间
ip netns exec ns1 bash
# 查看网络配置
ifconfig
eth0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.10.11 netmask 255.255.255.0 broadcast 192.168.10.255
ether 2a:0c:a8:b7:bc:32 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 4 bytes 240 (240.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4 bytes 240 (240.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
9、进入现有docker容器的网络命名空间
由于docker默认隐藏了/var/run/netns下的命名空间,因此需要做软链接,容器的1号进程的pid下的ns软链到/var/run/netns。
ln -sf /proc/<container-pid>/ns/net "/var/run/netns/<container-id>"
ip netns exec <container-id> bash
参考:
11.2.2 - Namespace命令介绍
Namespace相关命令
1. unshare
让进程进入一个新的namespace。
$ unshare --help
用法:
unshare [options] <program> [<argument>...]
Run a program with some namespaces unshared from the parent.
选项:
-m, --mount unshare mounts namespace
-u, --uts unshare UTS namespace (hostname etc)
-i, --ipc unshare System V IPC namespace
-n, --net unshare network namespace
-p, --pid unshare pid namespace
-U, --user unshare user namespace
-f, --fork fork before launching <program>
--mount-proc[=<dir>] mount proc filesystem first (implies --mount)
-r, --map-root-user map current user to root (implies --user)
--propagation <slave|shared|private|unchanged>
modify mount propagation in mount namespace
-s, --setgroups allow|deny control the setgroups syscall in user namespaces
-h, --help 显示此帮助并退出
-V, --version 输出版本信息并退出
更多信息请参阅 unshare(1)。
示例:
2. nsenter
进入某个namespace下运行某个进程。例如:docker exec -it <container_id> bash。
$ nsenter --help
用法:
nsenter [options] <program> [<argument>...]
Run a program with namespaces of other processes.
选项:
-t, --target <pid> 要获取名字空间的目标进程
-m, --mount[=<file>] enter mount namespace
-u, --uts[=<file>] enter UTS namespace (hostname etc)
-i, --ipc[=<file>] enter System V IPC namespace
-n, --net[=<file>] enter network namespace
-p, --pid[=<file>] enter pid namespace
-U, --user[=<file>] enter user namespace
-S, --setuid <uid> set uid in entered namespace
-G, --setgid <gid> set gid in entered namespace
--preserve-credentials do not touch uids or gids
-r, --root[=<dir>] set the root directory
-w, --wd[=<dir>] set the working directory
-F, --no-fork 执行 <程序> 前不 fork
-Z, --follow-context set SELinux context according to --target PID
-h, --help 显示此帮助并退出
-V, --version 输出版本信息并退出
更多信息请参阅 nsenter(1)。
11.3 - Cgroup
11.3.1 - Cgroup介绍
1. cgroup简介
Linux Cgroup提供了对一组进程及将来子进程的资源限制的能力。资源包括:CPU、内存、存储、网络等。通过Cgroup可以限制某个进程的资源占用,并监控进程的统计信息。
2. cgroup示例
1、创建一个hierarchy(cgroup树)
# 创建一个 hierarchy 挂载点
mkdir cgroup-test
# 挂载hierarchy 挂载点
mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test
# 查看生成的默认文件
# ll
总用量 0
-rw-r--r-- 1 root root 0 3月 5 19:13 cgroup.clone_children
--w--w--w- 1 root root 0 3月 5 19:13 cgroup.event_control
-rw-r--r-- 1 root root 0 3月 5 19:13 cgroup.procs
-r--r--r-- 1 root root 0 3月 5 19:13 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 3月 5 19:13 notify_on_release
-rw-r--r-- 1 root root 0 3月 5 19:13 release_agent
-rw-r--r-- 1 root root 0 3月 5 19:13 tasks
2、在根cgroup创建2个子cgroup
在cgroup目录下创建目录,子cgroup会继承父cgroup的属性。
mkdir cgroup-1
mkdir cgroup-2
# tree
.
├── cgroup-1
│ ├── cgroup.clone_children
│ ├── cgroup.event_control
│ ├── cgroup.procs
│ ├── notify_on_release
│ └── tasks
├── cgroup-2
│ ├── cgroup.clone_children
│ ├── cgroup.event_control
│ ├── cgroup.procs
│ ├── notify_on_release
│ └── tasks
├── cgroup.clone_children
├── cgroup.event_control
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
└── tasks
3、在cgroup中添加和移动进程。
echo $$ > tasks
4、通过subsystem限制cgroup中进程的资源。
系统为每个subsystem创建了一个默认的hierarchy。
3. cgroup限制CPU
1、启动进程,将cpu打到100%
# 启动进程,将cpu打到100%
stress-ng -c 1 --cpu-load 100 &
# 查看cpu占用
#ps auxw|grep stress
root 7607 0.0 0.0 56320 3620 pts/1 SL 10:35 0:00 stress-ng -c 1 --cpu-load 100
root 7608 100 0.0 56964 4076 pts/1 R 10:35 0:14 stress-ng -c 1 --cpu-load 100
# top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7608 root 20 0 56964 4076 1192 R 100.0 0.0 0:37.70 stress-ng-cpu
2、在/sys/fs/cgroup/cpu下创建子cgroup
# 创建子目录
cd /sys/fs/cgroup/cpu && mkdir cgroup-test
# 生成cgroup文件
/sys/fs/cgroup/cpu/cgroup-test#ll
总用量 0
-rw-r--r-- 1 root root 0 3月 9 20:28 cgroup.clone_children
--w--w--w- 1 root root 0 3月 9 20:28 cgroup.event_control
-rw-r--r-- 1 root root 0 3月 9 20:28 cgroup.procs
-r--r--r-- 1 root root 0 3月 9 20:28 cpuacct.bt_stat
-rw-r--r-- 1 root root 0 3月 9 20:28 cpuacct.bt_usage
-r--r--r-- 1 root root 0 3月 9 20:28 cpuacct.bt_usage_percpu
-r--r--r-- 1 root root 0 3月 9 20:28 cpuacct.stat
-r--r--r-- 1 root root 0 3月 9 20:28 cpuacct.uptime
-rw-r--r-- 1 root root 0 3月 9 20:28 cpuacct.usage
-r--r--r-- 1 root root 0 3月 9 20:28 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 3月 9 20:28 cpu.bt_shares
-rw-r--r-- 1 root root 0 3月 10 10:34 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 3月 9 20:29 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 3月 9 20:28 cpu.cfs_relax_thresh_sec
-rw-r--r-- 1 root root 0 3月 9 20:28 cpu.offline
-rw-r--r-- 1 root root 0 3月 9 20:28 cpu.rt_period_us
-rw-r--r-- 1 root root 0 3月 9 20:28 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 3月 9 20:28 cpu.shares
-r--r--r-- 1 root root 0 3月 9 20:28 cpu.stat
-rw-r--r-- 1 root root 0 3月 9 20:28 notify_on_release
-rw-r--r-- 1 root root 0 3月 10 10:32 tasks
3、限制进程的cpu
# 限制cpu比例为 cpu.cfs_quota_us/cpu.cfs_period_us,默认cpu.cfs_period_us为100000
cd /sys/fs/cgroup/cpu/cgroup-test
echo 50000 > cpu.cfs_quota_us # 限制cpu使用率为50%
# 将进程号写入tasks
echo 7608 > tasks
# 查看cpu使用率,可以看出cpu限制在50%
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7608 root 20 0 56964 4076 1192 R 50.2 0.0 9:08.24 stress-ng-cpu
4. cgroup限制内存
1、进入/sys/fs/cgroup/memory创建子cgroup
cd /sys/fs/cgroup/memory
mkdir cgroup-test
# 查看子cgroup生成内容
/sys/fs/cgroup/memory/cgroup-test#ll
总用量 0
-rw-r--r-- 1 root root 0 3月 10 10:51 cgroup.clone_children
--w--w--w- 1 root root 0 3月 10 10:51 cgroup.event_control
-rw-r--r-- 1 root root 0 3月 10 10:51 cgroup.procs
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.failcnt
--w------- 1 root root 0 3月 10 10:51 memory.force_empty
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 3月 10 10:51 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 3月 10 10:51 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 3月 10 10:51 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.max_usage_in_bytes
-r--r--r-- 1 root root 0 3月 10 10:51 memory.meminfo
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.memsw.failcnt
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.memsw.limit_in_bytes
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.memsw.max_usage_in_bytes
-r--r--r-- 1 root root 0 3月 10 10:51 memory.memsw.usage_in_bytes
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 3月 10 10:51 memory.numa_stat
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.oom_control
---------- 1 root root 0 3月 10 10:51 memory.pressure_level
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 3月 10 10:51 memory.stat
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.swappiness
-r--r--r-- 1 root root 0 3月 10 10:51 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 3月 10 10:51 memory.use_hierarchy
-r--r--r-- 1 root root 0 3月 10 10:51 memory.vmstat
-rw-r--r-- 1 root root 0 3月 10 10:51 notify_on_release
-rw-r--r-- 1 root root 0 3月 10 10:51 tasks
2、限制进程内存为100M
# 设置当前cgroup限制进程内存100M
cd /sys/fs/cgroup/memory/cgroup-tes
echo 100m > memory.limit_in_bytes
# 查看文件内容
/sys/fs/cgroup/memory/cgroup-test#cat memory.limit_in_bytes
104857600
3、启动测试进程
# 把当前的bash进程加入tasks
echo $$ > tasks
#cat tasks
7125
11980
# ps auxw|grep 7125
root 7125 0.0 0.0 115116 2588 pts/1 Ss 10:35 0:00 -bash
# 启动50M的进程
cd /sys/fs/cgroup/memory/cgroup-test
stress-ng --vm-bytes 50m --vm-keep -m 1 &
# 进程内存占用为50M,可正常运行
ps auxw|grep stress
root 20021 100 0.0 113932 58188 pts/1 R 10:58 2:13 stress-ng --vm-bytes 50m --vm-keep -m 1
# 改用启动200M的测试进程
stress-ng --vm-bytes 200m --vm-keep -m 1
# ps auxw|grep stress
root 29937 0.0 0.0 56320 3644 pts/1 SL 11:16 0:00 stress-ng --vm-bytes 200m --vm-keep -m 1
root 29938 0.1 0.0 56324 976 pts/1 S 11:16 0:00 stress-ng --vm-bytes 200m --vm-keep -m 1
# cat tasks
7125
29937
29938
32247
32248
4、进程OOM
tailf /var/log/messages
# OOM日志
Mar 10 11:19:17 kernel: stress-ng-vm invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=1000
Mar 10 11:19:17 kernel: stress-ng-vm cpuset=/ mems_allowed=0
Mar 10 11:19:17 kernel: CPU: 11 PID: 3691 Comm: stress-ng-vm Not tainted 3.10.107-1-tlinux2-0051 #1
Mar 10 11:19:17 kernel: Hardware name: Inspur SA5212M4/Shuyu, BIOS 4.1.13 01/24/2018
Mar 10 11:19:17 kernel: ffff880f5d92cd70 0000000094b48ef1 ffff880f2cee3c90 ffffffff819d9165
Mar 10 11:19:17 kernel: ffff880f2cee3cd0 ffffffff819d4957 000000002d514000 ffff880f5d92cd70
Mar 10 11:19:17 kernel: 0000000000000786 00000000000000d0 ffffffff81d73d78 0000000000006400
Mar 10 11:19:17 kernel: Call Trace:
Mar 10 11:19:17 kernel: [<ffffffff819d9165>] dump_stack+0x19/0x1b
Mar 10 11:19:17 kernel: [<ffffffff819d4957>] dump_header+0x79/0xb5
Mar 10 11:19:17 kernel: [<ffffffff81120aee>] oom_kill_process+0x24e/0x3a0
Mar 10 11:19:17 kernel: [<ffffffff8117b2f9>] mem_cgroup_oom_synchronize+0x4e9/0x510
Mar 10 11:19:17 kernel: [<ffffffff8117a330>] ? mem_cgroup_charge_common+0xc0/0xc0
Mar 10 11:19:17 kernel: [<ffffffff81121764>] pagefault_out_of_memory+0x14/0x90
Mar 10 11:19:17 kernel: [<ffffffff819d32dd>] mm_fault_error+0x67/0x140
Mar 10 11:19:17 kernel: [<ffffffff819e7ae6>] __do_page_fault+0x396/0x550
Mar 10 11:19:17 kernel: [<ffffffff8108335f>] ? set_next_entity+0x5f/0x80
Mar 10 11:19:17 kernel: [<ffffffff81084e63>] ? pick_next_task_fair+0x1c3/0x2d0
Mar 10 11:19:17 kernel: [<ffffffff819e7cae>] do_page_fault+0xe/0x10
Mar 10 11:19:17 kernel: [<ffffffff819e4422>] page_fault+0x22/0x30
Mar 10 11:19:17 kernel: Task in /cgroup-test killed as a result of limit of /cgroup-test
Mar 10 11:19:17 kernel: memory: usage 102400kB, limit 102400kB, failcnt 252574
Mar 10 11:19:17 kernel: memory+swap: usage 102400kB, limit 18014398509481983kB, failcnt 0
Mar 10 11:19:17 kernel: kmem: usage 0kB, limit 18014398509481983kB, failcnt 0
Mar 10 11:19:17 kernel: Memory cgroup stats for /cgroup-test: cache:101728KB rss:672KB rss_huge:0KB mapped_file:101728KB swap:0KB inactive_anon:101712KB active_anon:600KB inactive_file:0KB active_file:0KB unevictable:16KB
Mar 10 11:19:17 kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
Mar 10 11:19:17 kernel: [ 7125] 0 7125 28779 650 15 0 0 bash
Mar 10 11:19:17 kernel: [29937] 0 29937 14080 911 23 0 -1000 stress-ng
Mar 10 11:19:17 kernel: [29938] 0 29938 14081 244 23 0 -1000 stress-ng-vm
Mar 10 11:19:17 kernel: [ 3691] 0 3691 65281 24993 73 0 1000 stress-ng-vm
Mar 10 11:19:17 kernel: Memory cgroup out of memory: Kill process 3691 (stress-ng-vm) score 1926 or sacrifice child
Mar 10 11:19:17 kernel: Killed process 3691 (stress-ng-vm) total-vm:261124kB, anon-rss:292kB, file-rss:99680kB
Mar 10 11:19:17 cadvisor: I0310 11:19:17.952314 3143 manager.go:1178] Created an OOM event in container "/cgroup-test" at 2021-03-10 11:21:19.308776449 +0800 CST m=+150332.715342256
Mar 10 11:19:17 stress-ng: memory (MB): total 63979.26, free 58262.03, shared 0.00, buffer 481.01, swap 0.00, free swap 0.00
Mar 10 11:19:18 kernel: Memory cgroup out of memory: Kill process 3692 (stress-ng-vm) score 1926 or sacrifice child
Mar 10 11:19:18 kernel: Killed process 3692 (stress-ng-vm) total-vm:261124kB, anon-rss:292kB, file-rss:99680kB
Mar 10 11:19:18 stress-ng: memory (MB): total 63979.26, free 58262.15, shared 0.00, buffer 481.01, swap 0.00, free swap 0.00
参考:
11.3.2 - Cgroup命令介绍
cgroup常用命令
1. cgcreate
$ cgcreate --help
Usage: cgcreate [-h] [-f mode] [-d mode] [-s mode] [-t <tuid>:<tgid>] [-a <agid>:<auid>] -g <controllers>:<path> [-g ...]
Create control group(s)
-a <tuid>:<tgid> Owner of the group and all its files
-d, --dperm=mode Group directory permissions
-f, --fperm=mode Group file permissions
-g <controllers>:<path> Control group which should be added
-h, --help Display this help
-s, --tperm=mode Tasks file permissions
-t <tuid>:<tgid> Owner of the tasks file
示例:
cpu
# cgcreate -g cpu:cgrouptest
# ll /sys/fs/cgroup/cpu/cgrouptest
总用量 0
-rw-rw-r-- 1 root root 0 8月 15 20:14 cgroup.clone_children
--w--w---- 1 root root 0 8月 15 20:14 cgroup.event_control
-rw-rw-r-- 1 root root 0 8月 15 20:14 cgroup.procs
-r--r--r-- 1 root root 0 8月 15 20:14 cpuacct.stat
-r--r--r-- 1 root root 0 8月 15 20:14 cpuacct.uptime
-rw-rw-r-- 1 root root 0 8月 15 20:14 cpuacct.usage
-r--r--r-- 1 root root 0 8月 15 20:14 cpuacct.usage_percpu
-rw-rw-r-- 1 root root 0 8月 15 20:14 cpu.cfs_period_us
-rw-rw-r-- 1 root root 0 8月 15 20:14 cpu.cfs_quota_us
-rw-rw-r-- 1 root root 0 8月 15 20:14 cpu.cfs_relax_thresh_sec
-rw-rw-r-- 1 root root 0 8月 15 20:14 cpu.rt_period_us
-rw-rw-r-- 1 root root 0 8月 15 20:14 cpu.rt_runtime_us
-rw-rw-r-- 1 root root 0 8月 15 20:14 cpu.shares
-r--r--r-- 1 root root 0 8月 15 20:14 cpu.stat
-rw-rw-r-- 1 root root 0 8月 15 20:14 notify_on_release
-rw-rw-r-- 1 root root 0 8月 15 20:14 tasks
memory
# cgcreate -g memory:cgrouptest
# ll /sys/fs/cgroup/memory/cgrouptest
总用量 0
-rw-rw-r-- 1 root root 0 8月 15 20:16 cgroup.clone_children
--w--w---- 1 root root 0 8月 15 20:16 cgroup.event_control
-rw-rw-r-- 1 root root 0 8月 15 20:16 cgroup.procs
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.failcnt
--w--w---- 1 root root 0 8月 15 20:16 memory.force_empty
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.kmem.failcnt
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.kmem.limit_in_bytes
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 8月 15 20:16 memory.kmem.slabinfo
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.kmem.tcp.failcnt
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.kmem.tcp.limit_in_bytes
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 8月 15 20:16 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 8月 15 20:16 memory.kmem.usage_in_bytes
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.limit_in_bytes
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.max_usage_in_bytes
-r--r--r-- 1 root root 0 8月 15 20:16 memory.meminfo
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.memsw.failcnt
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.memsw.limit_in_bytes
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.memsw.max_usage_in_bytes
-r--r--r-- 1 root root 0 8月 15 20:16 memory.memsw.usage_in_bytes
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 8月 15 20:16 memory.numa_stat
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.oom_control
---------- 1 root root 0 8月 15 20:16 memory.pressure_level
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 8月 15 20:16 memory.stat
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.swappiness
-r--r--r-- 1 root root 0 8月 15 20:16 memory.usage_in_bytes
-rw-rw-r-- 1 root root 0 8月 15 20:16 memory.use_hierarchy
-r--r--r-- 1 root root 0 8月 15 20:16 memory.vmstat
-rw-rw-r-- 1 root root 0 8月 15 20:16 notify_on_release
-rw-rw-r-- 1 root root 0 8月 15 20:16 tasks
2. cgdelete
#cgdelete --help
Usage: cgdelete [-h] [-r] [[-g] <controllers>:<path>] ...
Remove control group(s)
-g <controllers>:<path> Control group to be removed (-g is optional)
-h, --help Display this help
-r, --recursive Recursively remove all subgroups
示例
cgdelete -g memory:/cgrouptest
cgdelete -g cpu:/cgrouptest
3. cgclassify
$ cgclassify --help
Usage: cgclassify [[-g] <controllers>:<path>] [--sticky | --cancel-sticky] <list of pids>
Move running task(s) to given cgroups
-h, --help Display this help
-g <controllers>:<path> Control group to be used as target
--cancel-sticky cgred daemon change pidlist and children tasks
--sticky cgred daemon does not change pidlist and children tasks
11.3.3 - Cgroup目录
1. cgroup的目录
/sys/fs/cgroup/
$ ll /sys/fs/cgroup/
总用量 0
drwxr-xr-x 6 root root 0 2月 18 14:31 blkio
lrwxrwxrwx 1 root root 11 2月 18 14:25 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 2月 18 14:25 cpuacct -> cpu,cpuacct
drwxr-xr-x 6 root root 0 2月 18 14:31 cpu,cpuacct
drwxr-xr-x 4 root root 0 2月 18 14:25 cpuset
drwxr-xr-x 6 root root 0 2月 18 14:31 devices
drwxr-xr-x 4 root root 0 2月 18 14:25 freezer
drwxr-xr-x 4 root root 0 2月 18 14:25 hugetlb
drwxr-xr-x 6 root root 0 2月 18 14:31 memory
drwxr-xr-x 4 root root 0 2月 18 14:25 net_cls
drwxr-xr-x 2 root root 0 2月 18 14:25 oom
drwxr-xr-x 4 root root 0 2月 18 14:25 perf_event
drwxr-xr-x 6 root root 0 2月 18 14:31 pids
drwxr-xr-x 6 root root 0 2月 18 14:25 systemd
2. docker中cgroup目录
2.1. cpu
/sys/fs/cgroup/cpu/docker/32a294d870965072acbf544da0c93a1692660d908bd72de43d1da48852083094
# ll /sys/fs/cgroup/cpu/docker/32a294d870965072acbf544da0c93a1692660d908bd72de43d1da48852083094
总用量 0
-rw-r--r-- 1 root root 0 7月 8 17:04 cgroup.clone_children
--w--w--w- 1 root root 0 7月 8 17:04 cgroup.event_control
-rw-r--r-- 1 root root 0 7月 8 17:04 cgroup.procs
-r--r--r-- 1 root root 0 7月 8 17:04 cpuacct.stat
-r--r--r-- 1 root root 0 7月 8 17:04 cpuacct.uptime
-rw-r--r-- 1 root root 0 7月 8 17:04 cpuacct.usage
-r--r--r-- 1 root root 0 7月 8 17:04 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 7月 8 17:04 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 7月 8 17:04 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 7月 8 17:04 cpu.cfs_relax_thresh_sec
-rw-r--r-- 1 root root 0 7月 8 17:04 cpu.rt_period_us
-rw-r--r-- 1 root root 0 7月 8 17:04 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 7月 8 17:04 cpu.shares
-r--r--r-- 1 root root 0 7月 8 17:04 cpu.stat
-rw-r--r-- 1 root root 0 7月 8 17:04 notify_on_release
-rw-r--r-- 1 root root 0 7月 8 17:04 tasks
2.2. memory
# ll /sys/fs/cgroup/memory/docker/32a294d870965072acbf544da0c93a1692660d908bd72de43d1da48852083094
总用量 0
-rw-r--r-- 1 root root 0 7月 8 17:04 cgroup.clone_children
--w--w--w- 1 root root 0 7月 8 17:04 cgroup.event_control
-rw-r--r-- 1 root root 0 7月 8 17:04 cgroup.procs
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.failcnt
--w------- 1 root root 0 7月 8 17:04 memory.force_empty
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 7月 8 17:04 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 7月 8 17:04 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 7月 8 17:04 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.max_usage_in_bytes
-r--r--r-- 1 root root 0 7月 8 17:04 memory.meminfo
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.memsw.failcnt
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.memsw.limit_in_bytes
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.memsw.max_usage_in_bytes
-r--r--r-- 1 root root 0 7月 8 17:04 memory.memsw.usage_in_bytes
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 7月 8 17:04 memory.numa_stat
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.oom_control
---------- 1 root root 0 7月 8 17:04 memory.pressure_level
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 7月 8 17:04 memory.stat
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.swappiness
-r--r--r-- 1 root root 0 7月 8 17:04 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 7月 8 17:04 memory.use_hierarchy
-r--r--r-- 1 root root 0 7月 8 17:04 memory.vmstat
-rw-r--r-- 1 root root 0 7月 8 17:04 notify_on_release
-rw-r--r-- 1 root root 0 7月 8 17:04 tasks
3. pod中cgroup目录
3.1. cpu
#ll /sys/fs/cgroup/cpu/kubepods/burstable/pode90435b5-4673-4bc2-9892-1f4825af5039/f62fb0f76b5b48cf903680296a1ba2abc314fdbf51e023886d06f8470d5ca90d
总用量 0
-rw-r--r-- 1 root root 0 8月 14 15:33 cgroup.clone_children
--w--w--w- 1 root root 0 8月 14 15:33 cgroup.event_control
-rw-r--r-- 1 root root 0 8月 14 15:33 cgroup.procs
-r--r--r-- 1 root root 0 8月 14 15:33 cpuacct.stat
-r--r--r-- 1 root root 0 8月 14 15:33 cpuacct.uptime
-rw-r--r-- 1 root root 0 8月 14 15:33 cpuacct.usage
-r--r--r-- 1 root root 0 8月 14 15:33 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 8月 14 15:33 cpu.cfs_period_us #
-rw-r--r-- 1 root root 0 8月 14 15:33 cpu.cfs_quota_us #
-rw-r--r-- 1 root root 0 8月 14 15:33 cpu.cfs_relax_thresh_sec
-rw-r--r-- 1 root root 0 8月 14 15:33 cpu.rt_period_us
-rw-r--r-- 1 root root 0 8月 14 15:33 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 8月 14 15:33 cpu.shares #
-r--r--r-- 1 root root 0 8月 14 15:33 cpu.stat
-rw-r--r-- 1 root root 0 8月 14 15:33 notify_on_release
-rw-r--r-- 1 root root 0 8月 14 15:33 tasks
3.2. memory
#ll /sys/fs/cgroup/memory/kubepods/burstable/pode90435b5-4673-4bc2-9892-1f4825af5039/f62fb0f76b5b48cf903680296a1ba2abc314fdbf51e023886d06f8470d5ca90d
总用量 0
-rw-r--r-- 1 root root 0 8月 14 15:33 cgroup.clone_children
--w--w--w- 1 root root 0 8月 14 15:33 cgroup.event_control
-rw-r--r-- 1 root root 0 8月 14 15:33 cgroup.procs
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.failcnt
--w------- 1 root root 0 8月 14 15:33 memory.force_empty
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 8月 14 15:33 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 8月 14 15:33 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 8月 14 15:33 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.max_usage_in_bytes
-r--r--r-- 1 root root 0 8月 14 15:33 memory.meminfo
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.memsw.failcnt
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.memsw.limit_in_bytes
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.memsw.max_usage_in_bytes
-r--r--r-- 1 root root 0 8月 14 15:33 memory.memsw.usage_in_bytes
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 8月 14 15:33 memory.numa_stat
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.oom_control
---------- 1 root root 0 8月 14 15:33 memory.pressure_level
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 8月 14 15:33 memory.stat
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.swappiness
-r--r--r-- 1 root root 0 8月 14 15:33 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 8月 14 15:33 memory.use_hierarchy
-r--r--r-- 1 root root 0 8月 14 15:33 memory.vmstat
-rw-r--r-- 1 root root 0 8月 14 15:33 notify_on_release
-rw-r--r-- 1 root root 0 8月 14 15:33 tasks
11.3.4 - Cgroup v2和v1的区别
本文主要介绍cgroup v2和v1的区别。
Cgroups(Control Groups)是 Linux 内核提供的一种资源限制、审计与隔离机制。它允许
将进程分组,并对这些组应用资源限制(如 CPU、内存、IO 等)。目前存在两个主要版本:cgroup v1 和 cgroup v2。
1. cgroup v1 和 v2 的主要区别
1.1. 层级结构设计
cgroup v1:
-
每个子系统(controller,如
cpu、memory)可以挂载在不同的层级上。 -
不同资源控制器可以拥有不同的层级结构。
-
同一个进程可以出现在多个层级中(因为各 controller 独立)。
缺点:
-
管理复杂:不同 controller 的层级可能冲突。
-
用户空间程序难以协调多个资源的管理。
cgroup v2:
-
所有 controller 使用统一的单层次树形结构(single unified hierarchy)。
-
每个进程只能属于一个 cgroup。
-
更简洁、更一致、更容易管理。
1.2. 资源控制行为改进
cgroup v1:
-
控制器之间独立实现,行为不一致。
-
有些 controller 行为奇怪或不完整(如 memory 的 oom 行为、cpu shares)。
cgroup v2:
-
控制器重写或增强,行为更加一致和合理。
-
更加严格的限制与传播模型,例如:
-
子 cgroup 的资源使用总量不能超过父级分配。
-
支持精细的 IO 带宽控制(通过
io.max取代 v1 的blkio控制器)。 -
内存高水位线通知 (
memory.high),支持 memory pressure 的渐进控制。
-
1.3. 配置接口(文件系统)
cgroup v1:
-
每个 controller 会暴露多个文件,如
memory.limit_in_bytes、cpu.shares。 -
文件名不一致、语义也不统一。
cgroup v2:
-
使用统一的接口命名,如:
-
memory.max替代memory.limit_in_bytes -
cpu.max替代cpu.cfs_quota_us等
-
-
配置方式更简洁、更一致。
1.4.进程行为控制
cgroup v1:
-
没有明显的限制,父级 cgroup 中的进程可以和子 cgroup 并存。
-
容易产生“叶子节点”和“中间节点”混用的问题。
cgroup v2:
-
中间节点不能包含进程,只有叶子节点可以运行进程。
-
强制遵循“控制权不可传递”,资源必须分配到底层。
1.5. 内核支持和兼容性
-
cgroup v1:默认启用在旧版本 Linux(如 CentOS 7)。
-
cgroup v2:Linux kernel 4.5 引入,5.x 逐渐成熟,现在主流发行版(如 Fedora、Ubuntu 22.04+)已默认使用 cgroup v2。
1.6. 总结对比表
| 特性 | cgroup v1 | cgroup v2 |
|---|---|---|
| 控制器挂载 | 多层级结构 | 单一统一层级 |
| 文件接口 | 各控制器自定义 | 统一命名和行为 |
| 进程限制 | 可混用中间/叶子节点 | 只能在叶子节点运行 |
| 控制器一致性 | 不一致,独立实现 | 统一,行为一致 |
| 内核支持 | 老系统默认使用 | 新内核推荐使用 |
2. Cgroup v2的资源隔离性优化
2.1. 统一的层级结构,消除隔离漏洞
📌 v1 问题:
-
每个 controller(如 cpu、memory、blkio)可以挂载在不同的层级。
-
一个进程可能出现在多个 cgroup(分别控制不同资源),导致资源隔离不一致或冲突。
- 例如:
cpu限制在/cg1,memory限制在/cg2,无法准确控制组合资源使用。
- 例如:
✅ v2 优化:
-
所有 controller 使用统一的单层级树(unified hierarchy)。
-
每个进程只能存在于一个 cgroup 中,所有资源限制统一生效。
-
隔离策略具有一致性和传递性,防止“某些资源没隔离”的情况。
🔍 好处: 避免多 controller 层级不一致造成的隔离漏洞,资源限制行为更可预期。
2.2. 增强的内存隔离机制
v2 新特性:
-
引入
memory.high(软限制):超过后触发页回收(但不立即 OOM)。 -
强化
memory.max(硬限制):超过直接触发 OOM。 -
支持 OOM group kill:可将整个 pod 内的多个进程作为一组统一 kill。
v1 缺陷:
- 只有
memory.limit_in_bytes,OOM 后果不可控,不能渐进响应内存压力。
🔍 好处:
-
实现更平滑的资源压力反馈,容器在压力下可“自救”。
-
防止 noisy neighbor(内存噪声邻居)干扰其他 pod。
2.3. 更精准的 CPU 调度控制
v2 改进:
-
使用
cpu.max设置 CPU 带宽上限,精度提升。 -
引入
cpu.weight(0~10000):代替 v1 的cpu.shares,线性、可加性更强。 -
支持 按比例调度,防止低优先 pod 抢占高优先资源。
v1 问题:
-
cpu.shares非线性、不稳定。 -
cfs_quota控制粒度粗,容易造成抖动。
🔍 好处: CPU 时间分配更加公平和稳定,防止容器之间 CPU 抢占影响彼此性能。
2.4. IO 隔离更细粒度和可控
v2 提供:
-
io.max:按设备设置带宽/IOPS 上限。 -
io.weight:设置权重调度优先级。
例如:
echo "8:0 rbps=1048576 wbps=1048576" > io.max # 限制 /dev/sda 读写速率为 1MB/s
v1 问题:
-
使用
blkio.*,行为依赖块设备,支持不一致,精度低。 -
IOPS 与带宽不能同时控制,难以部署混合 workload。
🔍 好处: 提供IO 抢占控制、防止磁盘打满拖垮其他 pod,对数据库/日志类容器特别有利。
2.5. 压力感知能力(PSI)提升感知性隔离
v2 新特性:
-
原生支持 Pressure Stall Information (PSI):
-
提供
cpu.pressure、memory.pressure、io.pressure等指标。 -
显示资源的阻塞比例、等待时间(如
some/fullstall)。
-
v1 不支持 PSI。
🔍 好处:
-
可用于调度器做资源感知(例如 Koordinator、Kubelet QRM)。
-
实现动态隔离/弹性扩缩容策略,提前感知资源瓶颈。
2.6. 进程放置约束提升层级隔离清晰度
v2 要求:
-
只有叶子节点可以运行进程。
-
中间节点用于组织和资源继承,避免"父子 cgroup 都有进程"的资源争用。
v1 无此限制,容易产生隔离混乱。
🔍 好处: 保证资源继承链明确,避免“父子抢资源”问题。
2.7. 更好的资源传播和继承规则
-
v2 明确规定:子 cgroup 的资源配额不可超过父级设置。
-
v1 中 controller 行为不一致,有些 controller(如
cpu)资源限制不会级联。
🔍 好处: 资源隔离链路完整,防止容器层越权使用 pod 层的资源限制之外的资源。
2.8. 资源隔离性提示对比
| 功能点 | cgroup v1 | cgroup v2 | 提升点 |
|---|---|---|---|
| 层级一致性 | ❌ 多层级 | ✅ 单层级 | 统一控制、隔离不混乱 |
| 内存隔离 | 一刀切、容易 OOM | 支持 memory.high + OOM group kill |
渐进隔离、防止雪崩 |
| CPU 限制 | 粒度粗、抢占明显 | 带宽精细 + weight 线性 | 可控公平调度 |
| IO 控制 | 粗糙、支持差 | io.max, io.weight 精细控制 |
IO 不互相影响 |
| PSI 支持 | ❌ | ✅ | 可用于资源压力调度 |
| 运行位置限制 | 无要求 | 只能在叶子节点 | 结构清晰,避免冲突 |
| 资源继承传播 | 弱、不一致 | 强、自动级联 | 隔离逻辑严密 |
3. cgroup v1 和 cgroup v2 的层级结构差异
场景:一个 Pod 内有两个容器 containerA 和 containerB
3.1. cgroup v1 的层级格式(多树结构)
在 cgroup v1 中,每个 controller(如 memory、cpu)是单独挂载的子系统,彼此独立,层级不一致:
# memory 子系统的 cgroup 层级:
/sys/fs/cgroup/memory/kubepods/pod123/containerA
/sys/fs/cgroup/memory/kubepods/pod123/containerB
# cpu 子系统的 cgroup 层级可能不同(可能是按 qos 再分层):
/sys/fs/cgroup/cpu/kubepods/burstable/pod123/containerA
/sys/fs/cgroup/cpu/kubepods/burstable/pod123/containerB
# blkio 也可能完全不同
/sys/fs/cgroup/blkio/k8s.io/pod123/containerA
/sys/fs/cgroup/blkio/k8s.io/pod123/containerB
问题:
-
资源限制配置分散,隔离策略难以统一。
-
kubelet 或 CRI 管理难度高,不易维护。
-
某些 controller 中,父子 cgroup 会混放进程,造成资源混抢。
3.2. cgroup v2 的层级格式(统一树结构)
cgroup v2 所有控制器(memory、cpu、io)使用统一的挂载点,并严格限制结构层次:
# 所有 controller 都在统一挂载点 /sys/fs/cgroup
/sys/fs/cgroup/kubepods.slice/
└── kubepods-burstable.slice/
└── kubepods-burstable-pod123.slice/
├── containerA.scope/
└── containerB.scope/
说明:
-
所有资源限制(CPU、memory、IO)都在
/sys/fs/cgroup/kubepods-burstable-pod123.slice/containerA.scope/这个目录中。 -
每个 container 的资源控制逻辑清晰统一。
-
不允许 container 和 pod 同时在一个目录中运行进程(叶子节点原则)。
3.3. 对比总结表
| 项目 | cgroup v1 | cgroup v2 |
|---|---|---|
| 控制器层级 | 每个控制器独立(多棵树) | 所有控制器统一(单棵树) |
| 示例目录结构 | memory/..., cpu/..., blkio/... | /sys/fs/cgroup/...(统一) |
| 资源隔离配置 | 分散在不同路径 | 集中在统一路径 |
| 父子节点进程混放 | 可能混放 | 严格禁止,父不能运行进程 |
| 管理复杂度 | 高,需多层次同步 | 低,集中管理 |
4. cgroup v2 常用资源限制文件汇总
4.1. CPU 控制器文件(cpu.*)
| 文件名 | 用途说明 | 示例值说明 |
|---|---|---|
cpu.max |
限制 CPU 最大使用带宽,格式为 quota period |
50000 100000:每 100ms 最多用 50ms,即 0.5 CPU |
cpu.weight |
设置 CPU 相对调度权重(1~10000) | 100(默认)权重越高调度越积极 |
cpu.stat |
CPU 使用统计 | 包含 usage_usec、throttled_usec 等累计指标 |
cpu.pressure |
CPU 资源压力监控(PSI) | some avg10=0.00 avg60=0.00 avg300=0.00 total=0 |
4.2. 内存控制器文件(memory.*)
| 文件名 | 用途说明 | 示例值说明 |
|---|---|---|
memory.max |
内存硬限制,超出则 OOM | 536870912 表示 512MB |
memory.high |
内存软限制,超过则触发回收(不 OOM) | 268435456 表示 256MB |
memory.low |
设置最低保证值,低于此值尽量不回收 | 134217728 表示 128MB |
memory.current |
当前使用内存 | 123456789 表示当前用约 117MB |
memory.stat |
内存使用细节统计 | 包括 anon、file、rss、slab、cache 等 |
memory.events |
记录 OOM、high 命中等事件次数 | high 1, oom 0 等 |
memory.pressure |
内存 PSI 指标 | some avg10=0.10 avg60=0.08 avg300=0.05 total=1234 |
4.3. IO 控制器文件(io.*)
| 文件名 | 用途说明 | 示例值说明 |
|---|---|---|
io.max |
限制块设备 IO 带宽/IOPS | 8:0 rbps=1048576 wbps=1048576 表示限速 1MB/s |
io.weight |
设置相对 IO 权重(1~10000) | 100(默认) |
io.stat |
实际 IO 统计(读写字节/次数) | 8:0 rbytes=12345678 wbytes=87654321 rios=123 wios=321 |
io.pressure |
IO PSI 指标 | some avg10=0.00 avg60=0.00 avg300=0.00 total=0 |
4.4. 进程数控制器文件(pids.*)
| 文件名 | 用途说明 | 示例值说明 |
|---|---|---|
pids.max |
限制最大进程/线程数 | 512:最多允许 512 个进程/线程 |
pids.current |
当前进程/线程数量 | 18:当前在此 cgroup 中运行的数量 |
4.5. 通用控制器文件(所有 cgroup 目录都会包含)
| 文件名 | 用途说明 | 示例值说明 |
|---|---|---|
cgroup.procs |
包含在该 cgroup 中的进程 PID | 1234\n5678 等 |
cgroup.threads |
包含线程 ID(TID) | 1234\n5678(不同于 PID) |
cgroup.controllers |
当前节点支持的 controller 列表 | cpu memory io pids |
cgroup.subtree_control |
子 cgroup 可继承的 controller 设置 | +cpu +memory +io(必须手动 echo 设置) |
cgroup.stat |
cgroup 统计信息 | nr_descendants=3 nr_dying_descendants=0 |
cgroup.events |
当前 cgroup 状态事件布尔值 | populated 1 memory.events oom=0 |
4.6. 高阶行为控制
常见于 systemd 管理或进程终止场景
| 文件名 | 用途说明 | 示例值说明 |
|---|---|---|
cgroup.freeze |
是否冻结此 cgroup 中所有进程(0=运行,1=冻结) | 0:未冻结 |
cgroup.kill |
向所有进程发送 SIGKILL,强制终止 | (执行 echo 1 > cgroup.kill) |
cgroup.type |
当前 cgroup 类型 | domain / threaded / domain threaded 等 |
5. Koordinator vs Volcano cgroup v2对比
Koordinator和Volcano是常用的在服务混布和调度方面的开源调度组件,以下是 Koordinator vs Volcano 在 cgroup v2 层面对 Pod 资源隔离增强能力的对比表格,从四大类资源(CPU、内存、IO、网络)出发,列出各自支持的能力、作用机制和相关的 cgroup v2 文件名。
| 类别 | 能力 | 说明 | Koordinator | Volcano | 使用的 cgroup v2 文件 |
|---|---|---|---|---|---|
| CPU | cpu.weight |
调整调度优先级,高权重获得更多 CPU 时间片 | ✅ | ✅ | cpu.weight |
cpu.max 限速 |
限制低优 pod 最大可用 CPU 带宽 | ✅ | ✅ | cpu.max |
|
| 动态 CPU Throttle | 根据压力指标自动调整低优 CPU 限速 | ✅(PSI 感知) | ❌ | cpu.max, cpu.pressure |
|
| PSI 感知调度 | 根据 cpu.pressure 控制 pod 的调度去向 |
✅ | ❌ | cpu.pressure |
|
| NUMA / CPU 绑核 | 结合 Topology 策略、QoS 实现高优绑核 | ✅ | ✅ | 非标准:cpuset.cpus(结合 CRI) |
|
| 内存 | memory.high 软限 |
超过此值将触发内核回收机制,避免高优被影响 | ✅ | ✅ | memory.high |
memory.low 保底 |
高优 pod 内存保底,不被回收 | ✅ | ❌ | memory.low |
|
| OOM Group Kill | 高优任务不被单独 kill,统一 OOM 策略 | ✅ | ✅ | memory.events, cgroup.kill |
|
| PSI 感知调度 | 通过 memory.pressure 控制任务调度,避免拥塞节点 |
✅ | ❌ | memory.pressure |
|
| IO | io.max 带宽限速 |
精确限制某容器的磁盘读写速率(rbps/wbps) | ✅ | ✅ | io.max |
io.weight 权重 |
调整 IO 调度优先级 | ✅ | ✅ | io.weight |
|
| IO PSI 感知调度 | 避免将 pod 调度到高 IO 压力节点 | ✅ | ❌ | io.pressure |
|
| 网络 | 网络速率控制(带宽) | 非 cgroup v2 标准,通过 eBPF/TC/net_cls 配合实现 | ✅(需扩展组件) | ❌ | 非标准:需 eBPF + tc + net_cls |
| 网络 NUMA 感知调度 | 网络与计算资源拓扑感知,避免带宽竞争 | ✅ | ❌ | 非标准(TopologyHints) | |
| 通用 | PSI 感知 | 综合调度参考 CPU/Memory/IO pressure | ✅ | ❌ | *.pressure |
| Freeze / Kill 控制 | 在极端负载时暂停或终止低优任务 | ✅(结合 memory.qos) | ✅ | cgroup.freeze, cgroup.kill |
|
| 容器级 QoS 管控 | 每个容器细粒度配置 resource policy | ✅ | ❌ | cpu.*, memory.*, io.* |
6. 配置 Kubernetes 使用 cgroup v2
6.1. 版本要求
| 条件/组件 | 要求/版本 |
|---|---|
| Linux 内核 | ≥ 5.4(推荐 ≥ 5.10) |
| containerd | ≥ 1.6,配置 SystemdCgroup = true |
| Kubernetes | ≥ 1.24,建议 1.28+ |
6.2. 配置 Kubernetes 使用 cgroup v2
配置 kubelet 使用 systemd 驱动(匹配 cgroup v2)
- kubelet 启动参数:
--cgroup-driver=systemd
- containerd 的配置文件(
/etc/containerd/config.toml):
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
- 重启 containerd:
systemctl restart containerd
或者
使用 kubeadm init 时建议显式指定:
kubeadm init --cgroup-driver=systemd ...
6.3. 验证 Kubernetes 是否运行在 cgroup v2
检查 cgroup 挂载类型:
mount | grep cgroup
# 输出如下则为cgroup v2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
验证 Pod 使用的是 cgroup v2 路径:
ps -eo pid,comm,cgroup | grep containerd-shim
输出中 cgroup 路径应以 /kubepods.slice/ 开头,无 cpu,cpuacct: 等逗号分隔子系统。
参考:
11.4 - Containerd
11.4.1 - 安装Containerd
1. Ubuntu安装containerd
以下以Ubuntu为例
说明:安装containerd与安装docker流程基本一致,差别在于不需要安装docker-ce
containerd: apt-get install -y containerd.iodocker: apt-get install docker-ce docker-ce-cli containerd.io
1. 卸载旧版本
sudo apt-get remove docker docker-engine docker.io containerd runc
如果需要删除镜像及容器数据则执行以下命令
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
2. 准备包环境
1、更新apt,允许使用https。
sudo apt-get update
sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release
2、添加docker官方GPG key。
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
3、设置软件仓库源
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
3. 安装containerd
# 安装containerd
sudo apt-get update
sudo apt-get install -y containerd.io
# 如果是安装docker则执行:
sudo apt-get install docker-ce docker-ce-cli containerd.io
# 查看运行状态
systemctl enable containerd
systemctl status containerd
安装指定版本
# 查看版本
apt-cache madison containerd
# sudo apt-get install containerd=<VERSION>
4. 修改配置
在 Linux 上,containerd 的默认 CRI 套接字是 /run/containerd/containerd.sock。
1、生成默认配置
containerd config default > /etc/containerd/config.toml
2、修改CgroupDriver为systemd
k8s官方推荐使用systemd类型的CgroupDriver。
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
3、重启containerd
systemctl restart containerd
2. 离线二进制安装containerd
把containerd、runc、cni-plugins、nerdctl二进制下载到本地,再上传到对应服务器,解压文件到对应目录,修改containerd配置文件,启动containerd。
#!/bin/bash
set -e
ContainerdVersion=$1
ContainerdVersion=${ContainerdVersion:-1.6.6}
RuncVersion=$2
RuncVersion=${RuncVersion:-1.1.3}
CniVersion=$3
CniVersion=${CniVersion:-1.1.1}
NerdctlVersion=$4
NerdctlVersion=${NerdctlVersion:-0.21.0}
CrictlVersion=$5
CrictlVersion=${CrictlVersion:-1.24.2}
echo "--------------install containerd--------------"
wget https://github.com/containerd/containerd/releases/download/v${ContainerdVersion}/containerd-${ContainerdVersion}-linux-amd64.tar.gz
tar Cxzvf /usr/local containerd-${ContainerdVersion}-linux-amd64.tar.gz
echo "--------------install containerd service--------------"
wget https://raw.githubusercontent.com/containerd/containerd/681aaf68b7dcbe08a51c3372cbb8f813fb4466e0/containerd.service
mv containerd.service /lib/systemd/system/
mkdir -p /etc/containerd/
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
echo "--------------install runc--------------"
wget https://github.com/opencontainers/runc/releases/download/v${RuncVersion}/runc.amd64
chmod +x runc.amd64
mv runc.amd64 /usr/local/bin/runc
echo "--------------install cni plugins--------------"
wget https://github.com/containernetworking/plugins/releases/download/v${CniVersion}/cni-plugins-linux-amd64-v${CniVersion}.tgz
rm -fr /opt/cni/bin
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v${CniVersion}.tgz
echo "--------------install nerdctl--------------"
wget https://github.com/containerd/nerdctl/releases/download/v${NerdctlVersion}/nerdctl-${NerdctlVersion}-linux-amd64.tar.gz
tar Cxzvf /usr/local/bin nerdctl-${NerdctlVersion}-linux-amd64.tar.gz
echo "--------------install crictl--------------"
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v${CrictlVersion}/crictl-v${CrictlVersion}-linux-amd64.tar.gz
tar Cxzvf /usr/local/bin crictl-v${CrictlVersion}-linux-amd64.tar.gz
cat > /etc/crictl.yaml << \EOF
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 2
debug: false
pull-image-on-create: false
EOF
# 启动containerd服务
systemctl daemon-reload
systemctl enable contaienrd
systemctl restart contaienrd
3. Containerd配置代理
由于节点到k8s官方仓库网络不通,或者设备处于内网,可以通过配置http_proxy代理的方式来拉取镜像。
vi /lib/systemd/system/containerd.service
# 添加代理环境变量
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target
[Service]
Environment="HTTP_PROXY=http://squid:3128/" # 添加环境变量代理
Environment="HTTPS_PROXY=http://squid:3128/" # 添加环境变量代理
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
# Comment TasksMax if your systemd version does not supports it.
# Only systemd 226 and above support this version.
TasksMax=infinity
OOMScoreAdjust=-999
[Install]
WantedBy=multi-user.target
# 重启服务
systemctl daemon-reload
systemctl restart containerd
参考:
11.4.2 - Containerd命令工具
crictl
#!/bin/bash
CrictlVersion=$1
CrictlVersion=${CrictlVersion:-1.24.2}
echo "--------------install crictl--------------"
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v${CrictlVersion}/crictl-v${CrictlVersion}-linux-amd64.tar.gz
tar Cxzvf /usr/local/bin nerdctl-${NerdctlVersion}-linux-amd64.tar.gz
设置配置文件
cat > /etc/crictl.yaml << \EOF
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 2
debug: false
pull-image-on-create: false
EOF
参考:
11.4.3 - 移除Dockershim
TODO
参考:
11.5 - Docker
11.5.1 - 安装Docker
1. CentOS 安装Docker
建议使用centos7
1.1. 安装Docker
1.1.1. 卸载旧版本
旧版本的Docker命名为docker或docker-engine,如果有安装旧版本,先卸载旧版本
$ sudo yum remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
1.1.2. 使用仓库安装
1、安装yum-utils、device-mapper-persistent-data、lvm2
$ sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
2、添加软件源
$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
1.1.3. 安装Docker
安装最新版本的Docker CE。
$ sudo yum install -y docker-ce
1.1.4. 启动Docker
# 启动Docker
$ sudo systemctl start docker
# 运行容器
$ sudo docker run hello-world
1.2. 安装指定版本Docker
1、列出可安装版本
$ yum list docker-ce --showduplicates | sort -r
docker-ce.x86_64 18.03.0.ce-1.el7.centos docker-ce-stable
2、安装指定版本
例如:docker-ce-18.03.0.ce
$ sudo yum install docker-ce-<VERSION STRING>
1.3. 升级Docker
依据1.2的方法选择指定版本安装。
1.4. 卸载Docker
# 卸载Docker
$ sudo yum remove docker-ce
# 清理镜像、容器、存储卷等
$ sudo rm -rf /var/lib/docker
2. Ubuntu 安装Docker
2.1. 安装Docker
2.1.1. 卸载旧版本
旧版本的Docker命名为docker或docker-engine,如果有安装旧版本,先卸载旧版本
sudo apt-get remove docker docker-engine docker.io
2.1.2. 使用仓库安装
1、升级apt
sudo apt-get update
2、允许apt使用https
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
3、添加Docker 官方的GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
4、添加Docker软件源
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
2.1.3. 安装Docker
# update
sudo apt-get update
# install docker
sudo apt-get install docker-ce
2.1.4. 启动Docker
# 设置为开机启动
sudo systemctl enable docker
# 启动docker
sudo systemctl start docker
2.2. 安装指定版本Docker
1、列出仓库的可安装版本,apt-cache madison docker-ce。
# apt-cache madison docker-ce
docker-ce | 18.06.0~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
docker-ce | 18.03.1~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
2、指定版本安装
例如:docker-ce=18.03.0~ce-0~ubuntu
sudo apt-get install docker-ce=<VERSION>
2.3. 升级Docker
# 更新源
sudo apt-get update
# 依据上述方法,指定版本安装
2.4. 卸载Docker
# 卸载 docker ce
sudo apt-get purge docker-ce
# 清理镜像、容器、存储卷等
sudo rm -rf /var/lib/docker
3. 离线rpm包安装Docker
3.1. 下载docker rpm包
rpm包地址:https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/
下载指定版本的containerd.io、docker-ce、docker-ce-cli
wget https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.6-3.3.el7.x86_64.rpm
wget https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/docker-ce-18.09.9-3.el7.x86_64.rpm
wget https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/docker-ce-cli-18.09.9-3.el7.x86_64.rpm
下载container-selinux
地址:http://mirror.centos.org/centos/7/extras/x86_64/Packages/
wget http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.107-3.el7.noarch.rpm
3.2. 安装rpm包
# container-selinux
rpm -ivh container-selinux*.rpm
# containerd.io
rpm -ivh containerd.io*.rpm
# docker-ce
rpm -ivh docker-ce*.rpm
# docker-ce-cli
rpm -ivh docker-ce-cli*.rpm
3.3. 启动docker服务
# 启动
systemctl start docker
# 查看状态
systemctl status docker
文章参考:
11.5.2 - Docker整体架构图
1. Docker的总架构图
docker是一个C/S模式的架构,后端是一个松耦合架构,模块各司其职。
- 用户是使用Docker Client与Docker Daemon建立通信,并发送请求给后者。
- Docker Daemon作为Docker架构中的主体部分,首先提供Server的功能使其可以接受Docker Client的请求;
- Engine执行Docker内部的一系列工作,每一项工作都是以一个Job的形式的存在。
- Job的运行过程中,当需要容器镜像时,则从Docker Registry中下载镜像,并通过镜像管理驱动graphdriver将下载镜像以Graph的形式存储;
- 当需要为Docker创建网络环境时,通过网络管理驱动networkdriver创建并配置Docker容器网络环境;
- 当需要限制Docker容器运行资源或执行用户指令等操作时,则通过execdriver来完成。
- libcontainer是一项独立的容器管理包,networkdriver以及execdriver都是通过libcontainer来实现具体对容器进行的操作。
2. Docker各模块组件分析
2.1. Docker Client[发起请求]
- Docker Client是和Docker Daemon建立通信的客户端。用户使用的可执行文件为docker(类似可执行脚本的命令),docker命令后接参数的形式来实现一个完整的请求命令(例如docker images,docker为命令不可变,images为参数可变)。
- Docker Client可以通过以下三种方式和Docker Daemon建立通信:tcp://host:port,unix://path_to_socket和fd://socketfd。
- Docker Client发送容器管理请求后,由Docker Daemon接受并处理请求,当Docker Client接收到返回的请求相应并简单处理后,Docker Client一次完整的生命周期就结束了。[一次完整的请求:发送请求→处理请求→返回结果],与传统的C/S架构请求流程并无不同。
2.2. Docker Daemon[后台守护进程]
Docker Daemon的架构图
2.2.1. Docker Server[调度分发请求]
Docker Server的架构图
- Docker Server相当于C/S架构的服务端。功能为接受并调度分发Docker Client发送的请求。接受请求后,Server通过路由与分发调度,找到相应的Handler来执行请求。
- 在Docker的启动过程中,通过包gorilla/mux,创建了一个mux.Router,提供请求的路由功能。在Golang中,gorilla/mux是一个强大的URL路由器以及调度分发器。该mux.Router中添加了众多的路由项,每一个路由项由HTTP请求方法(PUT、POST、GET或DELETE)、URL、Handler三部分组成。
- 创建完mux.Router之后,Docker将Server的监听地址以及mux.Router作为参数,创建一个httpSrv=http.Server{},最终执行httpSrv.Serve()为请求服务。
- 在Server的服务过程中,Server在listener上接受Docker Client的访问请求,并创建一个全新的goroutine来服务该请求。在goroutine中,首先读取请求内容,然后做解析工作,接着找到相应的路由项,随后调用相应的Handler来处理该请求,最后Handler处理完请求之后回复该请求。
2.2.2. Engine
- Engine是Docker架构中的运行引擎,同时也Docker运行的核心模块。它扮演Docker container存储仓库的角色,并且通过执行job的方式来操纵管理这些容器。
- 在Engine数据结构的设计与实现过程中,有一个handler对象。该handler对象存储的都是关于众多特定job的handler处理访问。举例说明,Engine的handler对象中有一项为:{"create": daemon.ContainerCreate,},则说明当名为"create"的job在运行时,执行的是daemon.ContainerCreate的handler。
2.2.3. Job
- 一个Job可以认为是Docker架构中Engine内部最基本的工作执行单元。Docker可以做的每一项工作,都可以抽象为一个job。例如:在容器内部运行一个进程,这是一个job;创建一个新的容器,这是一个job。Docker Server的运行过程也是一个job,名为serveapi。
- Job的设计者,把Job设计得与Unix进程相仿。比如说:Job有一个名称,有参数,有环境变量,有标准的输入输出,有错误处理,有返回状态等。
2.3. Docker Registry[镜像注册中心]
- Docker Registry是一个存储容器镜像的仓库(注册中心),可理解为云端镜像仓库,按repository来分类,docker pull 按照[repository]:[tag]来精确定义一个image。
- 在Docker的运行过程中,Docker Daemon会与Docker Registry通信,并实现搜索镜像、下载镜像、上传镜像三个功能,这三个功能对应的job名称分别为"search","pull" 与 "push"。
- 可分为公有仓库(docker hub)和私有仓库。
2.4. Graph[docker内部数据库]
Graph的架构图
2.4.1. Repository
- 已下载镜像的保管者(包括下载镜像和dockerfile构建的镜像)。
- 一个repository表示某类镜像的仓库(例如Ubuntu),同一个repository内的镜像用tag来区分(表示同一类镜像的不同标签或版本)。一个registry包含多个repository,一个repository包含同类型的多个image。
- 镜像的存储类型有aufs,devicemapper,Btrfs,Vfs等。其中centos系统使用devicemapper的存储类型。
- 同时在Graph的本地目录中,关于每一个的容器镜像,具体存储的信息有:该容器镜像的元数据,容器镜像的大小信息,以及该容器镜像所代表的具体rootfs。
2.4.2. GraphDB
- 已下载容器镜像之间关系的记录者。
- GraphDB是一个构建在SQLite之上的小型图数据库,实现了节点的命名以及节点之间关联关系的记录
2.5. Driver[执行部分]
Driver是Docker架构中的驱动模块。通过Driver驱动,Docker可以实现对Docker容器执行环境的定制。即Graph负责镜像的存储,Driver负责容器的执行。
2.5.1. graphdriver
graphdriver架构图
- graphdriver主要用于完成容器镜像的管理,包括存储与获取。
- 存储:docker pull下载的镜像由graphdriver存储到本地的指定目录(Graph中)。
- 获取:docker run(create)用镜像来创建容器的时候由graphdriver到本地Graph中获取镜像。
2.5.2. networkdriver
networkdriver的架构图
- networkdriver的用途是完成Docker容器网络环境的配置,其中包括
- Docker启动时为Docker环境创建网桥;
- Docker容器创建时为其创建专属虚拟网卡设备;
- Docker容器分配IP、端口并与宿主机做端口映射,设置容器防火墙策略等。
2.5.3. execdriver
execdriver的架构图
- execdriver作为Docker容器的执行驱动,负责创建容器运行命名空间,负责容器资源使用的统计与限制,负责容器内部进程的真正运行等。
- 现在execdriver默认使用native驱动,不依赖于LXC。
2.6. libcontainer[函数库]
libcontainer的架构图
- libcontainer是Docker架构中一个使用Go语言设计实现的库,设计初衷是希望该库可以不依靠任何依赖,直接访问内核中与容器相关的API。
- Docker可以直接调用libcontainer,而最终操纵容器的namespace、cgroups、apparmor、网络设备以及防火墙规则等。
- libcontainer提供了一整套标准的接口来满足上层对容器管理的需求。或者说,libcontainer屏蔽了Docker上层对容器的直接管理。
2.7. docker container[服务交付的最终形式]
container架构
-
Docker container(Docker容器)是Docker架构中服务交付的最终体现形式。
-
Docker按照用户的需求与指令,订制相应的Docker容器:
-
- 用户通过指定容器镜像,使得Docker容器可以自定义rootfs等文件系统;
- 用户通过指定计算资源的配额,使得Docker容器使用指定的计算资源;
- 用户通过配置网络及其安全策略,使得Docker容器拥有独立且安全的网络环境;
- 用户通过指定运行的命令,使得Docker容器执行指定的工作。
参考文章:
- 《Docker源码分析》
11.5.3 - Docker常用命令原理图
1. 基本概念
1.1. image layer(镜像层)
镜像可以看成是由多个镜像层叠加起来的一个文件系统,镜像层也可以简单理解为一个基本的镜像,而每个镜像层之间通过指针的形式进行叠加。

根据上图,镜像层的主要组成部分包括镜像层id,镜像层指针【指向父层】,元数据【layer metadata】包含了docker构建和运行的信息还有父层的层次信息。
只读层和读写层【top layer】的组成部分基本一致。同时读写层可以转换成只读层【docker commit操作实现】
1.2. image(镜像)---【只读层的集合】
1、镜像是一堆只读层的统一视角,除了最底层没有指向外,每一层都指向它的父层,统一文件系统(union file system)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。而每一层都是不可写的,就是只读层。

1.3. container(容器)---【一层读写层+多层只读层】
1、容器和镜像的区别在于容器的最上面一层是读写层【top layer】,而这边并没有区分容器是否在运行。运行状态的容器【running container】即一个可读写的文件系统【静态容器】+隔离的进程空间和其中的进程。

隔离的进程空间中的进程可以对该读写层进行增删改,其运行状态容器的进程操作都作用在该读写层上。每个容器只能有一个进程隔离空间。

2. Docker常用命令原理图概览:
3. Docker常用命令说明
3.1. 标识说明
3.1.1. image---(统一只读文件系统)

3.1.2. 静态容器【未运行的容器】---(统一可读写文件系统)

3.1.3. 动态容器【running container】---(进程空间(包括进程)+统一可读写文件系统)

3.2. 命令说明
3.2.1. docker生命周期相关命令:
3.2.1.1. docker create {image-id}

即为只读文件系统添加一层可读写层【top layer】,生成可读写文件系统,该命令状态下容器为静态容器,并没有运行。
3.2.1.2. docker start(restart) {container-id}
docker stop即为docker start的逆过程

即为可读写文件系统添加一个进程空间【包括进程】,生成动态容器【running container】
3.2.1.3. docker run {image-id}

docker run=docker create+docker start
类似流程如下 :

3.2.1.4. docker stop {container-id}

向运行的容器中发一个SIGTERM的信号,然后停止所有的进程。即为docker start的逆过程。
3.2.1.5. docker kill {container-id}

docker kill向容器发送不友好的SIGKILL的信号,相当于快速强制关闭容器,与docker stop的区别在于docker stop是正常关闭,先发SIGTERM信号,清理进程,再发SIGKILL信号退出。
3.2.1.6. docker pause {container-id}
docker unpause为逆过程---比较少使用

暂停容器中的所有进程,使用cgroup的freezer顺序暂停容器里的所有进程,docker unpause为逆过程即恢复所有进程。比较少使用。
3.2.1.7. docker commit {container-id}


把容器的可读写层转化成只读层,即从容器状态【可读写文件系统】变为镜像状态【只读文件系统】,可理解为【固化】。
3.2.1.8. docker build


docker build=docker run【运行容器】+【进程修改数据】+docker commit【固化数据】,不断循环直至生成所需镜像。
循环一次便会形成新的层(镜像)【原镜像层+已固化的可读写层】
docker build 一般作用在dockerfile文件上。
3.2.2. docker查询类命令
查询对象:①image,②container,③image/container中的数据,④系统信息[容器数,镜像数及其他]
3.2.2.1. Image
1、docker images

docker images 列出当前镜像【以顶层镜像id来表示整个完整镜像】,每个顶层镜像下面隐藏多个镜像层。
2、docker images -a

docker images -a列出所有镜像层【排序以每个顶层镜像id为首后接该镜像下的所有镜像层】,依次列出每个镜像的所有镜像层。
3、docker history {image-id}

docker history 列出该镜像id下的所有历史镜像。
3.2.2.2. Container
1、docker ps

列出所有运行的容器【running container】
2、docker ps -a

列出所有容器,包括静态容器【未运行的容器】和动态容器【running container】
3.2.2.3. Info
1、docker inspect {container-id} or {image-id}

提取出容器或镜像最顶层的元数据。
2、docker info
显示 Docker 系统信息,包括镜像和容器数。
3.2.3. docker操作类命令:
3.2.3.1. docker rm {container-id}

docker rm会移除镜像,该命令只能对静态容器【非运行状态】进行操作。
通过docker rm -f {container-id}的-f (force)参数可以强制删除运行状态的容器【running container】。
3.2.3.2. docker rmi {image-id}

3.2.3.3. docker exec {running-container-id}

docker exec会在运行状态的容器中执行一个新的进程。
3.2.3.4. docker export {container-id}

docker export命令创建一个tar文件,并且移除了元数据和不必要的层,将多个层整合成了一个层,只保存了当前统一视角看到的内容。
参考文章:
11.5.4 - Dockerfile使用说明
1. Dockerfile的说明
dockerfile指令忽略大小写,建议大写,#作为注释,每行只支持一条指令,指令可以带多个参数。
dockerfile指令分为构建指令和设置指令。
- 构建指令:用于构建image,其指定的操作不会在运行image的容器中执行。
- 设置指令:用于设置image的属性,其指定的操作会在运行image的容器中执行。
2. Dockerfile指令说明
2.1. FROM(指定基础镜像)[构建指令]
该命令用来指定基础镜像,在基础镜像的基础上修改数据从而构建新的镜像。基础镜像可以是本地仓库也可以是远程仓库。
指令有两种格式:
- FROM
image【默认为latest版本】 - FROM
image:tag【指定版本】
2.2. MAINTAINER(镜像创建者信息)[构建指令]
将镜像制作者(维护者)的信息写入image中,执行docker inspect时会输出该信息。
格式:MAINTAINER name
MAINTAINER命令已废弃,可使用maintainer label的方式。
LABEL maintainer="SvenDowideit@home.org.au"
2.3. RUN(安装软件用)[构建指令]
RUN可以运行任何被基础镜像支持的命令(即在基础镜像上执行一个进程),可以使用多条RUN指令,指令较长可以使用\来换行。
指令有两种格式:
- RUN
command(the command is run in a shell -/bin/sh -c) - RUN ["executable", "param1", "param2" ... ] (exec form)
- 指定使用其他终端实现,使用exec执行。
- 例子:RUN["/bin/bash","-c","echo hello"]
2.4. CMD(设置container启动时执行的操作)[设置指令]
用于容器启动时的指定操作,可以是自定义脚本或命令,只执行一次,多个默认执行最后一个。
指令有三种格式:
- CMD ["executable","param1","param2"] (like an exec, this is the preferred form)
- 运行一个可执行文件并提供参数。
- CMD command param1 param2 (as a shell)
- 直接执行shell命令,默认以/bin/sh -c执行。
- CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
- 和ENTRYPOINT配合使用,只作为完整命令的参数部分。
2.5. ENTRYPOINT(设置container启动时执行的操作)[设置指令]
指定容器启动时执行的命令,若多次设置只执行最后一次。
ENTRYPOINT翻译为“进入点”,它的功能可以让容器表现得像一个可执行程序一样。
例子:ENTRYPOINT ["/bin/echo"] ,那么docker build出来的镜像以后的容器功能就像一个/bin/echo程序,docker run -it imageecho “this is a test”,就会输出对应的字符串。这个imageecho镜像对应的容器表现出来的功能就像一个echo程序一样。
指令有两种格式:
-
ENTRYPOINT ["executable", "param1", "param2"] (like an exec, the preferred form)
-
和CMD配合使用,CMD则作为完整命令的参数部分,ENTRYPOINT以JSON格式指定执行的命令部分。CMD可以为ENTRYPOINT提供可变参数,不需要变动的参数可以写在ENTRYPOINT里面。
-
例子:
ENTRYPOINT ["/usr/bin/ls","-a"]
CMD ["-l"]
-
-
ENTRYPOINT command param1 param2 (as a shell)
- 独自使用,即和CMD类似,如果CMD也是个完整命令[CMD command param1 param2 (as a shell) ],那么会相互覆盖,只执行最后一个CMD或ENTRYPOINT。
- 例子:ENTRYPOINT ls -l
2.6. USER(设置container容器启动的登录用户)[设置指令]
设置启动容器的用户,默认为root用户。
格式:USER daemon
2.7. EXPOSE(指定容器需要映射到宿主机的端口)[设置指令]
该指令会将容器中的端口映射为宿主机中的端口[确保宿主机的端口号没有被使用]。通过宿主机IP和映射后的端口即可访问容器[避免每次运行容器时IP随机生成不固定的问题]。前提是EXPOSE设置映射端口,运行容器时加上-p参数指定EXPOSE设置的端口。EXPOSE可以设置多个端口号,相应地运行容器配套多次使用-p参数。可以通过docker port +容器需要映射的端口号和容器ID来参考宿主机的映射端口。
格式:EXPOSE port [port...]
2.8. ENV(用于设置环境变量)[构建指令]
在image中设置环境变量[以键值对的形式],设置之后RUN命令可以使用该环境变量,在容器启动后也可以通过docker inspect查看环境变量或者通过 docker run --env key=value设置或修改环境变量。
格式:ENV key value
例子:ENV JAVA_HOME /path/to/java/dirent
2.9. ARG(用于设置变量)[构建指令]
ARG定义一个默认参数,可以在dockerfile中引用。构建阶段可以通过docker build --build-arg
ARG <arg_name>[=<default value>]
# 可以搭配ENV使用
ENV env_name ${arg_name}
示例:
docker build --build-arg user=what_user .
2.10. ADD(从src复制文件到container的dest路径)[构建指令]
复制指定的src到容器中的dest,其中src是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件url。dest 是container中的绝对路径。所有拷贝到container中的文件和文件夹权限为0755,uid和gid为0。
- 如果src是一个目录,那么会将该目录下的所有文件添加到container中,不包括目录;
- 如果src文件是可识别的压缩格式,则docker会帮忙解压缩(注意压缩格式);
- 如果
src是文件且dest中不使用斜杠结束,则会将dest视为文件,src的内容会写入dest; - 如果
src是文件且dest中使用斜杠结束,则会src文件拷贝到dest目录下。
格式:ADD src dest
为避免 ADD命令带来的未知风险和复杂性,可以使用COPY命令替代ADD命令
2.11. COPY(复制文件)
复制本地主机的src为容器中的dest,目标路径不存在时会自动创建。
格式:COPY src dest
2.12. VOLUME(指定挂载点)[设置指令]
创建一个可以从本地主机或其他容器挂载的挂载点,使容器中的一个目录具有持久化存储数据的功能,该目录可以被容器本身使用也可以被其他容器使用。
格式:VOLUME ["mountpoint"]
其他容器使用共享数据卷:docker run -t -i -rm -volumes-from container1 image2 bash [container1为第一个容器的ID,image2为第二个容器运行image的名字。]
2.13. WORKDIR(切换目录)[设置指令]
相当于cd命令,可以多次切换目录,为RUN,CMD,ENTRYPOINT配置工作目录。可以使用多个WORKDIR的命令,后续命令如果是相对路径则是在上一级路径的基础上执行[类似cd的功能]。
格式:WORKDIR /path/to/workdir
2.14. ONBUILD(在子镜像中执行)
当所创建的镜像作为其他新创建镜像的基础镜像时执行的操作命令,即在创建本镜像时不运行,当作为别人的基础镜像时再在构建时运行(可认为基础镜像为父镜像,而该命令即在它的子镜像构建时运行,相当于在子镜像构建时多加了一些命令)。
格式:ONBUILD Dockerfile关键字
3. dockerfile示例
最佳实践
- 镜像可以分为三层:系统基础镜像、业务基础镜像、业务镜像。
- 尽量将不变的镜像操作放dockerfile前面。
- 一类RUN命令操作可以通过
\和&&方式组合成一条RUN命令。 - dockerfile尽量清晰简洁。
文件目录
./
|-- Dockerfile
|-- docker-entrypoint.sh
|-- dumb-init
|-- conf # 配置文件路径
| `-- app_conf.py
|-- pkg # 安装包路径
| `-- install.tar.gz
|-- run.sh # 启动脚本
dockerfile示例
FROM centos:latest
LABEL maintainer="xxx@xxx.com"
ARG APP=appname
ENV APP ${APP}
# copy and install app
COPY conf/app_conf.py /usr/local/app/app_conf/app_conf.py
COPY pkg/${APP}-*-install.tar.gz /data/${APP}-install.tar.gz
RUN mkdir -p /data/${APP} \
&& tar -zxvf /data/${APP}-install.tar.gz -C /data/${APP} \
&& cd /data/${APP}/${APP}* \
&& ./install.sh
WORKDIR /usr/local/app/
# init
COPY dumb-init /usr/bin/dumb-init
COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/usr/bin/dumb-init", "--","/docker-entrypoint.sh"]
COPY run.sh /run.sh
RUN chmod +x /run.sh
CMD ["/run.sh"]
4. docker build
指定dockerfile文件构建
默认不指定dockerfile文件名,则读取指定路径的Dockerfile
docker build -t <image_name> -f <dockerfile_name> <dockerfile_path>
docker build --help
docker build --help
Usage: docker build [OPTIONS] PATH | URL | -
Build an image from a Dockerfile
Options:
--add-host list Add a custom host-to-IP mapping (host:ip)
--build-arg list Set build-time variables
--cache-from strings Images to consider as cache sources
--cgroup-parent string Optional parent cgroup for the container
--compress Compress the build context using gzip
--cpu-period int Limit the CPU CFS (Completely Fair Scheduler) period
--cpu-quota int Limit the CPU CFS (Completely Fair Scheduler) quota
-c, --cpu-shares int CPU shares (relative weight)
--cpuset-cpus string CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1)
--disable-content-trust Skip image verification (default true)
-f, --file string Name of the Dockerfile (Default is 'PATH/Dockerfile')
--force-rm Always remove intermediate containers
--iidfile string Write the image ID to the file
--isolation string Container isolation technology
--label list Set metadata for an image
-m, --memory bytes Memory limit
--memory-swap bytes Swap limit equal to memory plus swap: '-1' to enable unlimited swap
--network string Set the networking mode for the RUN instructions during build (default "default")
--no-cache Do not use cache when building the image
--pull Always attempt to pull a newer version of the image
-q, --quiet Suppress the build output and print image ID on success
--rm Remove intermediate containers after a successful build (default true)
--security-opt strings Security options
--shm-size bytes Size of /dev/shm
-t, --tag list Name and optionally a tag in the 'name:tag' format
--target string Set the target build stage to build.
--ulimit ulimit Ulimit options (default [])
参考:
11.6 - Kata Container
11.6.1 - Kata容器简介
Kata-container简介
kata-container通过轻量型虚拟机技术构建一个安全的容器运行时,表现像容器一样,但通硬件虚拟化技术提供强隔离,作为第二层的安全防护。
特点:
- 安全:独立的内核,提供网络、I/O、内存的隔离。
- 兼容性:支持OCI容器标准,k8s的CRI接口。
- 性能:兼容虚拟机的安全和容器的轻量特点。
- 简单:使用标准的接口。
1. kata-container架构
kata-container与传统container的比较
2. kata-runtime
Kata Containers runtime (kata-runtime)通过QEMU*/KVM技术创建了一种轻量型的虚拟机,兼容 OCI runtime specification 标准,支持Kubernetes* Container Runtime Interface (CRI)接口,可替换CRI shim runtime (runc) 通过k8s来创建pod或容器。
3. shim
shim类似Docker的 containerd-shim 或CRI-O的 conmon,主要用来监控和回收容器的进程,kata-shim需要处理所有的容器的IO流(stdout, stdin and stderr)和转发相关信号。
containerd-shim-kata-v2实现了Containerd Runtime V2 (Shim API),k8s可以通过containerd-shim-kata-v2(替代2N+1个shims[由一个containerd-shim和kata-shim组成])来创建pod。
4. kata-agent
在虚拟机内kata-agent作为一个daemon进程运行,并拉起容器的进程。kata-agent使用VIRTIO或VSOCK接口(QEMU在主机上暴露的socket文件)在guest虚拟机中运行gRPC服务器。kata-runtime通过grpc协议与kata-agent通信,向kata-agent发送管理容器的命令。该协议还用于容器和管理引擎(例如Docker Engine)之间传送I / O流(stdout,stderr,stdin)。
容器内所有的执行命令和相关的IO流都需要通过QEMU在宿主机暴露的virtio-serial或vsock接口,当使用VIRTIO的情况下,每个虚拟机会创建一个Kata Containers proxy (kata-proxy) 来处理命令和IO流。
kata-agent使用libcontainer 来管理容器的生命周期,复用了runc的部分代码。
5. kata-proxy
kata-proxy提供了 kata-shim 和 kata-runtime 与VM中的kata-agent通信的方式,其中通信方式是使用virtio-serial或vsock,默认是使用virtio-serial。
6. Hypervisor
kata-container通过QEMU/KVM来创建虚拟机给容器运行,可以支持多种hypervisors。
7. QEMU/KVM
待补充
参考文档:
11.6.2 - kata配置
1. 配置文件路径
默认的配置文件位于/usr/share/defaults/kata-containers/configuration.toml,如果/etc/kata-containers/configuration.toml的配置文件存在,则会替代默认的配置文件。
查看配置文件的路径命令如下:
# kata-runtime --kata-show-default-config-paths
/etc/kata-containers/configuration.toml
/usr/share/defaults/kata-containers/configuration.toml
指定自定义配置文件运行
kata-runtime --kata-config=/some/where/configuration.toml ...
2. kata-env
查看runtime使用到的环境参数,
kata-runtime kata-env
输出内容如下:
[Meta]
Version = "1.0.23"
[Runtime]
Debug = false
Trace = false
DisableGuestSeccomp = true
DisableNewNetNs = false
Path = "/usr/bin/kata-runtime"
[Runtime.Version]
Semver = "1.7.2"
Commit = "9b9282693cfbcf70d442916bea56771cc6fc3afe"
OCI = "1.0.1-dev"
[Runtime.Config]
Path = "/usr/share/defaults/kata-containers/configuration.toml"
[Hypervisor]
MachineType = "pc"
Version = "QEMU emulator version 2.11.0\nCopyright (c) 2003-2017 Fabrice Bellard and the QEMU Project developers"
Path = "/usr/bin/qemu-lite-system-x86_64"
BlockDeviceDriver = "virtio-scsi"
EntropySource = "/dev/urandom"
Msize9p = 8192
MemorySlots = 10
Debug = false
UseVSock = false
SharedFS = "virtio-9p"
[Image]
Path = "/usr/share/kata-containers/kata-containers-image_centos_1.7.2_agent_20190702.img"
[Kernel]
Path = "/usr/share/kata-containers/vmlinuz-4.19.28.42-6.1.container"
Parameters = "init=/usr/lib/systemd/systemd systemd.unit=kata-containers.target systemd.mask=systemd-networkd.service systemd.mask=systemd-networkd.socket systemd.mask=systemd-journald.service systemd.mask=systemd-journald.socket systemd.mask=systemd-journal-flush.service systemd.mask=systemd-journald-dev-log.socket systemd.mask=systemd-udevd.service systemd.mask=systemd-udevd.socket systemd.mask=systemd-udev-trigger.service systemd.mask=systemd-udevd-kernel.socket systemd.mask=systemd-udevd-control.socket systemd.mask=systemd-timesyncd.service systemd.mask=systemd-update-utmp.service systemd.mask=systemd-tmpfiles-setup.service systemd.mask=systemd-tmpfiles-cleanup.service systemd.mask=systemd-tmpfiles-cleanup.timer systemd.mask=tmp.mount systemd.mask=systemd-random-seed.service systemd.mask=systemd-coredump@.service"
[Initrd]
Path = ""
[Proxy]
Type = "kataProxy"
Version = "kata-proxy version 1.7.2-a56df7c"
Path = "/usr/libexec/kata-containers/kata-proxy"
Debug = false
[Shim]
Type = "kataShim"
Version = "kata-shim version 1.7.2-2ea178c"
Path = "/usr/libexec/kata-containers/kata-shim"
Debug = false
[Agent]
Type = "kata"
Debug = false
Trace = false
TraceMode = ""
TraceType = ""
[Host]
Kernel = "4.14.105-1-tlinux3-0008"
Architecture = "amd64"
VMContainerCapable = true
SupportVSocks = true
[Host.Distro]
Name = "Tencent tlinux"
Version = "2.2"
[Host.CPU]
Vendor = "GenuineIntel"
Model = "Intel(R) Xeon(R) CPU X3440 @ 2.53GHz"
[Netmon]
Version = "kata-netmon version 1.7.2"
Path = "/usr/libexec/kata-containers/kata-netmon"
Debug = false
Enable = false
3. configuration.toml
# Copyright (c) 2017-2019 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
# XXX: WARNING: this file is auto-generated.
# XXX:
# XXX: Source file: "cli/config/configuration-qemu.toml.in"
# XXX: Project:
# XXX: Name: Kata Containers
# XXX: Type: kata
[hypervisor.qemu]
path = "/usr/bin/qemu-lite-system-x86_64"
kernel = "/usr/share/kata-containers/vmlinuz.container"
image = "/usr/share/kata-containers/kata-containers.img"
machine_type = "pc"
# Optional space-separated list of options to pass to the guest kernel.
# For example, use `kernel_params = "vsyscall=emulate"` if you are having
# trouble running pre-2.15 glibc.
#
# WARNING: - any parameter specified here will take priority over the default
# parameter value of the same name used to start the virtual machine.
# Do not set values here unless you understand the impact of doing so as you
# may stop the virtual machine from booting.
# To see the list of default parameters, enable hypervisor debug, create a
# container and look for 'default-kernel-parameters' log entries.
kernel_params = ""
# Path to the firmware.
# If you want that qemu uses the default firmware leave this option empty
firmware = ""
# Machine accelerators
# comma-separated list of machine accelerators to pass to the hypervisor.
# For example, `machine_accelerators = "nosmm,nosmbus,nosata,nopit,static-prt,nofw"`
machine_accelerators=""
# Default number of vCPUs per SB/VM:
# unspecified or 0 --> will be set to 1
# < 0 --> will be set to the actual number of physical cores
# > 0 <= number of physical cores --> will be set to the specified number
# > number of physical cores --> will be set to the actual number of physical cores
default_vcpus = 1
# Default maximum number of vCPUs per SB/VM:
# unspecified or == 0 --> will be set to the actual number of physical cores or to the maximum number
# of vCPUs supported by KVM if that number is exceeded
# > 0 <= number of physical cores --> will be set to the specified number
# > number of physical cores --> will be set to the actual number of physical cores or to the maximum number
# of vCPUs supported by KVM if that number is exceeded
# WARNING: Depending of the architecture, the maximum number of vCPUs supported by KVM is used when
# the actual number of physical cores is greater than it.
# WARNING: Be aware that this value impacts the virtual machine's memory footprint and CPU
# the hotplug functionality. For example, `default_maxvcpus = 240` specifies that until 240 vCPUs
# can be added to a SB/VM, but the memory footprint will be big. Another example, with
# `default_maxvcpus = 8` the memory footprint will be small, but 8 will be the maximum number of
# vCPUs supported by the SB/VM. In general, we recommend that you do not edit this variable,
# unless you know what are you doing.
default_maxvcpus = 0
# Bridges can be used to hot plug devices.
# Limitations:
# * Currently only pci bridges are supported
# * Until 30 devices per bridge can be hot plugged.
# * Until 5 PCI bridges can be cold plugged per VM.
# This limitation could be a bug in qemu or in the kernel
# Default number of bridges per SB/VM:
# unspecified or 0 --> will be set to 1
# > 1 <= 5 --> will be set to the specified number
# > 5 --> will be set to 5
default_bridges = 1
# Default memory size in MiB for SB/VM.
# If unspecified then it will be set 2048 MiB.
default_memory = 2048
#
# Default memory slots per SB/VM.
# If unspecified then it will be set 10.
# This is will determine the times that memory will be hotadded to sandbox/VM.
#memory_slots = 10
# The size in MiB will be plused to max memory of hypervisor.
# It is the memory address space for the NVDIMM devie.
# If set block storage driver (block_device_driver) to "nvdimm",
# should set memory_offset to the size of block device.
# Default 0
#memory_offset = 0
# Disable block device from being used for a container's rootfs.
# In case of a storage driver like devicemapper where a container's
# root file system is backed by a block device, the block device is passed
# directly to the hypervisor for performance reasons.
# This flag prevents the block device from being passed to the hypervisor,
# 9pfs is used instead to pass the rootfs.
disable_block_device_use = false
# Shared file system type:
# - virtio-9p (default)
# - virtio-fs
shared_fs = "virtio-9p"
# Path to vhost-user-fs daemon.
virtio_fs_daemon = "/usr/bin/virtiofsd-x86_64"
# Default size of DAX cache in MiB
virtio_fs_cache_size = 1024
# Cache mode:
#
# - none
# Metadata, data, and pathname lookup are not cached in guest. They are
# always fetched from host and any changes are immediately pushed to host.
#
# - auto
# Metadata and pathname lookup cache expires after a configured amount of
# time (default is 1 second). Data is cached while the file is open (close
# to open consistency).
#
# - always
# Metadata, data, and pathname lookup are cached in guest and never expire.
virtio_fs_cache = "always"
# Block storage driver to be used for the hypervisor in case the container
# rootfs is backed by a block device. This is virtio-scsi, virtio-blk
# or nvdimm.
block_device_driver = "virtio-scsi"
# Specifies cache-related options will be set to block devices or not.
# Default false
#block_device_cache_set = true
# Specifies cache-related options for block devices.
# Denotes whether use of O_DIRECT (bypass the host page cache) is enabled.
# Default false
#block_device_cache_direct = true
# Specifies cache-related options for block devices.
# Denotes whether flush requests for the device are ignored.
# Default false
#block_device_cache_noflush = true
# Enable iothreads (data-plane) to be used. This causes IO to be
# handled in a separate IO thread. This is currently only implemented
# for SCSI.
#
enable_iothreads = false
# Enable pre allocation of VM RAM, default false
# Enabling this will result in lower container density
# as all of the memory will be allocated and locked
# This is useful when you want to reserve all the memory
# upfront or in the cases where you want memory latencies
# to be very predictable
# Default false
#enable_mem_prealloc = true
# Enable huge pages for VM RAM, default false
# Enabling this will result in the VM memory
# being allocated using huge pages.
# This is useful when you want to use vhost-user network
# stacks within the container. This will automatically
# result in memory pre allocation
#enable_hugepages = true
# Enable swap of vm memory. Default false.
# The behaviour is undefined if mem_prealloc is also set to true
#enable_swap = true
# This option changes the default hypervisor and kernel parameters
# to enable debug output where available. This extra output is added
# to the proxy logs, but only when proxy debug is also enabled.
#
# Default false
#enable_debug = true
# Disable the customizations done in the runtime when it detects
# that it is running on top a VMM. This will result in the runtime
# behaving as it would when running on bare metal.
#
#disable_nesting_checks = true
# This is the msize used for 9p shares. It is the number of bytes
# used for 9p packet payload.
#msize_9p = 8192
# If true and vsocks are supported, use vsocks to communicate directly
# with the agent and no proxy is started, otherwise use unix
# sockets and start a proxy to communicate with the agent.
# Default false
#use_vsock = true
# VFIO devices are hotplugged on a bridge by default.
# Enable hotplugging on root bus. This may be required for devices with
# a large PCI bar, as this is a current limitation with hotplugging on
# a bridge. This value is valid for "pc" machine type.
# Default false
#hotplug_vfio_on_root_bus = true
# If host doesn't support vhost_net, set to true. Thus we won't create vhost fds for nics.
# Default false
#disable_vhost_net = true
#
# Default entropy source.
# The path to a host source of entropy (including a real hardware RNG)
# /dev/urandom and /dev/random are two main options.
# Be aware that /dev/random is a blocking source of entropy. If the host
# runs out of entropy, the VMs boot time will increase leading to get startup
# timeouts.
# The source of entropy /dev/urandom is non-blocking and provides a
# generally acceptable source of entropy. It should work well for pretty much
# all practical purposes.
#entropy_source= "/dev/urandom"
# Path to OCI hook binaries in the *guest rootfs*.
# This does not affect host-side hooks which must instead be added to
# the OCI spec passed to the runtime.
#
# You can create a rootfs with hooks by customizing the osbuilder scripts:
# https://github.com/kata-containers/osbuilder
#
# Hooks must be stored in a subdirectory of guest_hook_path according to their
# hook type, i.e. "guest_hook_path/{prestart,postart,poststop}".
# The agent will scan these directories for executable files and add them, in
# lexicographical order, to the lifecycle of the guest container.
# Hooks are executed in the runtime namespace of the guest. See the official documentation:
# https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#posix-platform-hooks
# Warnings will be logged if any error is encountered will scanning for hooks,
# but it will not abort container execution.
#guest_hook_path = "/usr/share/oci/hooks"
[factory]
# VM templating support. Once enabled, new VMs are created from template
# using vm cloning. They will share the same initial kernel, initramfs and
# agent memory by mapping it readonly. It helps speeding up new container
# creation and saves a lot of memory if there are many kata containers running
# on the same host.
#
# When disabled, new VMs are created from scratch.
#
# Note: Requires "initrd=" to be set ("image=" is not supported).
#
# Default false
#enable_template = true
# Specifies the path of template.
#
# Default "/run/vc/vm/template"
#template_path = "/run/vc/vm/template"
# The number of caches of VMCache:
# unspecified or == 0 --> VMCache is disabled
# > 0 --> will be set to the specified number
#
# VMCache is a function that creates VMs as caches before using it.
# It helps speed up new container creation.
# The function consists of a server and some clients communicating
# through Unix socket. The protocol is gRPC in protocols/cache/cache.proto.
# The VMCache server will create some VMs and cache them by factory cache.
# It will convert the VM to gRPC format and transport it when gets
# requestion from clients.
# Factory grpccache is the VMCache client. It will request gRPC format
# VM and convert it back to a VM. If VMCache function is enabled,
# kata-runtime will request VM from factory grpccache when it creates
# a new sandbox.
#
# Default 0
#vm_cache_number = 0
# Specify the address of the Unix socket that is used by VMCache.
#
# Default /var/run/kata-containers/cache.sock
#vm_cache_endpoint = "/var/run/kata-containers/cache.sock"
[proxy.kata]
path = "/usr/libexec/kata-containers/kata-proxy"
# If enabled, proxy messages will be sent to the system log
# (default: disabled)
#enable_debug = true
[shim.kata]
path = "/usr/libexec/kata-containers/kata-shim"
# If enabled, shim messages will be sent to the system log
# (default: disabled)
#enable_debug = true
# If enabled, the shim will create opentracing.io traces and spans.
# (See https://www.jaegertracing.io/docs/getting-started).
#
# Note: By default, the shim runs in a separate network namespace. Therefore,
# to allow it to send trace details to the Jaeger agent running on the host,
# it is necessary to set 'disable_new_netns=true' so that it runs in the host
# network namespace.
#
# (default: disabled)
#enable_tracing = true
[agent.kata]
# If enabled, make the agent display debug-level messages.
# (default: disabled)
#enable_debug = true
# Enable agent tracing.
#
# If enabled, the default trace mode is "dynamic" and the
# default trace type is "isolated". The trace mode and type are set
# explicity with the `trace_type=` and `trace_mode=` options.
#
# Notes:
#
# - Tracing is ONLY enabled when `enable_tracing` is set: explicitly
# setting `trace_mode=` and/or `trace_type=` without setting `enable_tracing`
# will NOT activate agent tracing.
#
# - See https://github.com/kata-containers/agent/blob/master/TRACING.md for
# full details.
#
# (default: disabled)
#enable_tracing = true
#
#trace_mode = "dynamic"
#trace_type = "isolated"
[netmon]
# If enabled, the network monitoring process gets started when the
# sandbox is created. This allows for the detection of some additional
# network being added to the existing network namespace, after the
# sandbox has been created.
# (default: disabled)
#enable_netmon = true
# Specify the path to the netmon binary.
path = "/usr/libexec/kata-containers/kata-netmon"
# If enabled, netmon messages will be sent to the system log
# (default: disabled)
#enable_debug = true
[runtime]
# If enabled, the runtime will log additional debug messages to the
# system log
# (default: disabled)
#enable_debug = true
#
# Internetworking model
# Determines how the VM should be connected to the
# the container network interface
# Options:
#
# - bridged
# Uses a linux bridge to interconnect the container interface to
# the VM. Works for most cases except macvlan and ipvlan.
#
# - macvtap
# Used when the Container network interface can be bridged using
# macvtap.
#
# - none
# Used when customize network. Only creates a tap device. No veth pair.
#
# - tcfilter
# Uses tc filter rules to redirect traffic from the network interface
# provided by plugin to a tap interface connected to the VM.
#
internetworking_model="tcfilter"
# disable guest seccomp
# Determines whether container seccomp profiles are passed to the virtual
# machine and applied by the kata agent. If set to true, seccomp is not applied
# within the guest
# (default: true)
disable_guest_seccomp=true
# If enabled, the runtime will create opentracing.io traces and spans.
# (See https://www.jaegertracing.io/docs/getting-started).
# (default: disabled)
#enable_tracing = true
# If enabled, the runtime will not create a network namespace for shim and hypervisor processes.
# This option may have some potential impacts to your host. It should only be used when you know what you're doing.
# `disable_new_netns` conflicts with `enable_netmon`
# `disable_new_netns` conflicts with `internetworking_model=bridged` and `internetworking_model=macvtap`. It works only
# with `internetworking_model=none`. The tap device will be in the host network namespace and can connect to a bridge
# (like OVS) directly.
# If you are using docker, `disable_new_netns` only works with `docker run --net=none`
# (default: false)
#disable_new_netns = true
# Enabled experimental feature list, format: ["a", "b"].
# Experimental features are features not stable enough for production,
# They may break compatibility, and are prepared for a big version bump.
# Supported experimental features:
# 1. "newstore": new persist storage driver which breaks backward compatibility,
# expected to move out of experimental in 2.0.0.
# (default: [])
experimental=[]
参考:
11.7 - WasmEdge介绍
1. Wasm(WebAssembly)是什么
Wasm,全称为WebAssembly,是基于堆栈的虚拟机的二进制指令格式。Wasm被设计为编程语言的可移植编译目标,支持在Web上部署客户端和服务器应用程序。
WebAssembly的主要目标是提供一种可移植、高效、安全的执行环境,以在Web浏览器中运行各种编程语言的代码。它不依赖于特定的硬件或操作系统,WebAssembly允许开发人员使用多种编程语言,例如C、C++、Rust等,通过编译成Wasm字节码来在Web上运行。
Wasm的特点:
-
高效性能:Wasm被设计为高效执行,并且与底层系统硬件紧密关联,使其在Web浏览器中可以获得接近本机代码的性能。
-
安全性:Wasm是一种隔离的执行环境,它运行在浏览器的沙箱中,具有严格的安全性措施,确保Wasm代码不能直接访问Web浏览器的敏感资源和功能。
-
可移植性:由于Wasm是一种独立于平台的中间表示,因此可以在各种设备和操作系统上运行,从桌面计算机到移动设备。
-
语言无关性:Wasm允许使用多种编程语言编写代码,而不仅限于JavaScript。这为开发人员提供了更多的灵活性,使得在Web上运行高性能应用程序变得更加容易。
一句话来概括:
Wasm是一种可移植、高效、安全、跨语言的二进制编码格式。它支持在客户端(浏览器)和服务端运行应用程序。
2. WasmEdge是什么
WasmEdge 是一个轻量级、高性能和可扩展的 WebAssembly 运行时。它是当今最快的Wasm VM。适用于云原生、边缘和去中心化应用程序。它为serverless应用程序、嵌入式功能、微服务、智能合约和 IoT 设备提供支持。
3. 如何将golang编译成wasm并运行
代码如下:
package main
func main() {
println("Hello TinyGo from WasmEdge!")
}
3.1. 编译wasm二进制
使用tinygo编译
安装tinygo
ubuntu系统
wget https://github.com/tinygo-org/tinygo/releases/download/v0.28.1/tinygo_0.28.1_amd64.deb
sudo dpkg -i tinygo_0.28.1_amd64.deb
tinygo编译
tinygo build -o hello.wasm -target wasm main.go
3.2. 运行wasm二进制
安装wasmedge,参考:https://wasmedge.org/docs/start/install
wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -p /usr/local
运行wasm二进制
参考:
[Go - WasmEdge Runtime](Go - WasmEdge Runtime)
# wasmedge hello.wasm
Hello TinyGo from WasmEdge!
3.3. 性能提升
要为这些应用程序达到原生 Go 性能,你可以使用 wasmedgec 命令来 AOT 编译 wasm 程序,然后使用 wasmedge 命令运行它。
$ wasmedgec hello.wasm hello.wasm
$ wasmedge hello.wasm
Hello TinyGo from WasmEdge!
4. 如何构建wasm的容器镜像
安装buildah,参考:https://github.com/containers/buildah/blob/main/install.md
sudo apt-get -y update
sudo apt-get -y install buildah
步骤如下:
-
编译wasm二进制
-
编写dockerfile,例如:
FROM scratch COPY hello.wasm / CMD ["/hello.wasm"] -
使用buildah构建和发布镜像。
buildah build --annotation "module.wasm.image/variant=compat-smart" -t wasm-hello .
参考:
12 - Etcd集群
12.1 - Etcd介绍
1. Etcd是什么(what)
etcd is a distributed, consistent key-value store for shared configuration and service discovery, with a focus on being:
- Secure: automatic TLS with optional client cert authentication[可选的SSL客户端证书认证:支持https访问 ]
- Fast: benchmarked 10,000 writes/sec[单实例每秒 1000 次写操作]
- Reliable: properly distributed using Raft[使用Raft保证一致性]
etcd是一个分布式、一致性的键值存储系统,主要用于配置共享和服务发现。[以上内容来自etcd官网]
2. 为什么使用Etcd(why)
2.1. Etcd的优势
- 简单。使用Go语言编写部署简单;使用HTTP作为接口使用简单;使用Raft算法保证强一致性让用户易于理解。
- 数据持久化。etcd默认数据一更新就进行持久化。
- 安全。etcd支持SSL客户端安全认证。
3. 如何实现Etcd架构(how)
3.1. Etcd的相关名词解释
- Raft:etcd所采用的保证分布式系统强一致性的算法。
- Node:一个Raft状态机实例。
- Member: 一个etcd实例。它管理着一个Node,并且可以为客户端请求提供服务。
- Cluster:由多个Member构成可以协同工作的etcd集群。
- Peer:对同一个etcd集群中另外一个Member的称呼。
- Client: 向etcd集群发送HTTP请求的客户端。
- WAL:预写式日志,etcd用于持久化存储的日志格式。
- snapshot:etcd防止WAL文件过多而设置的快照,存储etcd数据状态。
- Proxy:etcd的一种模式,为etcd集群提供反向代理服务。
- Leader:Raft算法中通过竞选而产生的处理所有数据提交的节点。
- Follower:竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证。
- Candidate:当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始竞选。【候选人】
- Term:某个节点成为Leader到下一次竞选时间,称为一个Term。【任期】
- Index:数据项编号。Raft中通过Term和Index来定位数据。
3.2. Etcd的架构图

一个用户的请求发送过来,会经由HTTP Server转发给Store进行具体的事务处理,如果涉及到节点的修改,则交给Raft模块进行状态的变更、日志的记录,然后再同步给别的etcd节点以确认数据提交,最后进行数据的提交,再次同步。
1、HTTP Server:
用于处理用户发送的API请求以及其它etcd节点的同步与心跳信息请求。
2、Raft:
Raft强一致性算法的具体实现,是etcd的核心。
3、WAL:
Write Ahead Log(预写式日志),是etcd的数据存储方式,用于系统提供原子性和持久性的一系列技术。除了在内存中存有所有数据的状态以及节点的索引以外,etcd就通过WAL进行持久化存储。WAL中,所有的数据提交前都会事先记录日志。
-
Entry[日志内容]:
负责存储具体日志的内容。
-
Snapshot[快照内容]:
Snapshot是为了防止数据过多而进行的状态快照,日志内容发生变化时保存Raft的状态。
4、Store:
用于处理etcd支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是etcd对用户提供的大多数API功能的具体实现。
12.2 - 部署Etcd集群
12.2.1 - 使用etcdadm部署Etcd集群
1. 安装etcdadm
在Releases · kubernetes-sigs/etcdadm · GitHub中选择需要部署的版本,示例如下:
wget https://github.com/kubernetes-sigs/etcdadm/releases/download/v0.1.5/etcdadm-linux-amd64
mv etcdadm-linux-amd64 /usr/bin/etcdadm
chmod +x /usr/bin/etcdadm
2. 部署etcd集群
2.1. init
etcd的版本可以在 Releases · etcd-io/etcd · GitHub 中查询。
etcdadm init --name <node1> --version=3.5.4
2.2. 上传证书到其他机器
# 登录node2 node3
mkdir -p /etc/etcd/pki
# 将node1的/etc/etcd/pki/ca.* 拷贝到node2 node3 /etc/etcd/pki/
scp /etc/etcd/pki/ca.* node2:/etc/etcd/pki/
2.3. join
etcdadm join https://<node1>:2379 --name=<node2> --version=3.5.4
etcdadm join https://<node1>:2379 --name=<node3> --version=3.5.4
3. 查看集群状态
设置etcdctl环境变量
# 添加endpoint
cat >> /etc/etcd/etcdctl.env << EOF
export ETCDCTL_ENDPOINTS=node1:2379,node2:2379,node2:2379
EOF
# 拷贝命令脚本到/usr/bin/
cp /opt/bin/etcdctl /opt/bin/etcdctl.sh /usr/bin/
查看集群状态
$ etcdctl.sh endpoint status -w table
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| node1:2379 | 5fe84cb4a0ef4e69 | 3.5.4 | 20 kB | true | false | 3 | 13 | 13 | |
| node2:2379 | cb8d48da0ea9b8c0 | 3.5.4 | 20 kB | false | false | 3 | 13 | 13 | |
| node3:2379 | fafa80c55eebeffa | 3.5.4 | 20 kB | false | false | 3 | 13 | 13 | |
+--------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
4. Etcd启动配置文件
systemd service
# cat /etc/systemd/system/etcd.service
[Unit]
Description=etcd
Documentation=https://github.com/coreos/etcd
Conflicts=etcd-member.service
Conflicts=etcd2.service
[Service]
EnvironmentFile=/etc/etcd/etcd.env
ExecStart=/opt/bin/etcd
Type=notify
TimeoutStartSec=0
Restart=on-failure
RestartSec=5s
LimitNOFILE=65536
Nice=-10
IOSchedulingClass=best-effort
IOSchedulingPriority=2
MemoryLow=200M
[Install]
WantedBy=multi-user.target
/etc/etcd/etcd.env
ETCD_NAME=etcd01
# Initial cluster configuration
ETCD_INITIAL_CLUSTER=etcd01=https://node1:2380
ETCD_INITIAL_CLUSTER_TOKEN=88ad6def
ETCD_INITIAL_CLUSTER_STATE=new
# Peer configuration
ETCD_INITIAL_ADVERTISE_PEER_URLS=https://node1:2380
ETCD_LISTEN_PEER_URLS=https://node1:2380
ETCD_CLIENT_CERT_AUTH=true
ETCD_PEER_CERT_FILE=/etc/etcd/pki/peer.crt
ETCD_PEER_KEY_FILE=/etc/etcd/pki/peer.key
ETCD_PEER_TRUSTED_CA_FILE=/etc/etcd/pki/ca.crt
# Client/server configuration
ETCD_ADVERTISE_CLIENT_URLS=https://node1:2379
ETCD_LISTEN_CLIENT_URLS=https://node1:2379,https://127.0.0.1:2379
ETCD_PEER_CLIENT_CERT_AUTH=true
ETCD_CERT_FILE=/etc/etcd/pki/server.crt
ETCD_KEY_FILE=/etc/etcd/pki/server.key
ETCD_TRUSTED_CA_FILE=/etc/etcd/pki/ca.crt
# Other
ETCD_DATA_DIR=/var/lib/etcd
ETCD_STRICT_RECONFIG_CHECK=true
GOMAXPROCS=48
参考:
12.3 - Raft算法
1. Raft协议[分布式一致性算法]

raft算法中涉及三种角色,分别是:
follower: 跟随者candidate: 候选者,选举过程中的中间状态角色leader: 领导者
2. 过程
2.1. 选举
有两个timeout来控制选举,第一个是election timeout,该时间是节点从follower到成为candidate的时间,该时间是150到300毫秒之间的随机值。另一个是heartbeat timeout。
- 当某个节点经历完
election timeout成为candidate后,开启新的一个选举周期,他向其他节点发起投票请求(Request Vote),如果接收到消息的节点在该周期内还没投过票则给这个candidate投票,然后节点重置他的election timeout。 - 当该candidate获得大部分的选票,则可以当选为leader。
- leader就开始发送
append entries给其他follower节点,这个消息会在内部指定的heartbeat timeout时间内发出,follower收到该信息则响应给leader。 - 这个选举周期会继续,直到某个follower没有收到心跳,并成为candidate。
- 如果某个选举周期内,有两个candidate同时获得相同多的选票,则会等待一个新的周期重新选举。
2.2. 同步
当选举过程结束,选出了leader,则leader需要把所有的变更同步的系统中的其他节点,该同步也是通过发送Append Entries的消息的方式。
- 首先一个客户端发送一个更新给leader,这个更新会添加到leader的日志中。
- 然后leader会在给follower的下次心跳探测中发送该更新。
- 一旦大多数follower收到这个更新并返回给leader,leader提交这个更新,然后返回给客户端。
2.3. 网络分区
- 当发生网络分区的时候,在不同分区的节点接收不到leader的心跳,则会开启一轮选举,形成不同leader的多个分区集群。
- 当客户端给不同leader的发送更新消息时,不同分区集群中的节点个数小于原先集群的一半时,更新不会被提交,而节点个数大于集群数一半时,更新会被提交。
- 当网络分区恢复后,被提交的更新会同步到其他的节点上,其他节点未提交的日志会被回滚并匹配新leader的日志,保证全局的数据是一致的。
参考:
12.4 - etcdctl命令工具
12.4.1 - etcdctl-V3
etcdctl的v3版本与v2版本使用命令有所不同,本文介绍etcdctl v3版本的命令工具的使用方式。
1. etcdctl的安装
etcdctl的二进制文件可以在 github.com/coreos/etcd/releases 选择对应的版本下载,例如可以执行以下install_etcdctl.sh的脚本,修改其中的版本信息。
#!/bin/bash
ETCD_VER=v3.3.4
ETCD_DIR=etcd-download
DOWNLOAD_URL=https://github.com/coreos/etcd/releases/download
# Download
mkdir ${ETCD_DIR}
cd ${ETCD_DIR}
wget ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar -xzvf etcd-${ETCD_VER}-linux-amd64.tar.gz
# install
cd etcd-${ETCD_VER}-linux-amd64
cp etcdctl /usr/local/bin/
2. etcdctl V3
使用etcdctlv3的版本时,需设置环境变量ETCDCTL_API=3。
export ETCDCTL_API=3
# 或者在`/etc/profile`文件中添加环境变量
vi /etc/profile
...
export ETCDCTL_API=3
...
source /etc/profile
# 或者在命令执行前加 ETCDCTL_API=3
ETCDCTL_API=3 etcdctl --endpoints=$ENDPOINTS member list
查看当前etcdctl的版本信息etcdctl version。
[root@k8s-dbg-master-1 etcd]# etcdctl version
etcdctl version: 3.3.4
API version: 3.3
更多命令帮助可以查询etcdctl —help。
[root@k8s-dbg-master-1 etcd]# etcdctl --help
NAME:
etcdctl - A simple command line client for etcd3.
USAGE:
etcdctl
VERSION:
3.3.4
API VERSION:
3.3
COMMANDS:
get Gets the key or a range of keys
put Puts the given key into the store
del Removes the specified key or range of keys [key, range_end)
txn Txn processes all the requests in one transaction
compaction Compacts the event history in etcd
alarm disarm Disarms all alarms
alarm list Lists all alarms
defrag Defragments the storage of the etcd members with given endpoints
endpoint health Checks the healthiness of endpoints specified in `--endpoints` flag
endpoint status Prints out the status of endpoints specified in `--endpoints` flag
endpoint hashkv Prints the KV history hash for each endpoint in --endpoints
move-leader Transfers leadership to another etcd cluster member.
watch Watches events stream on keys or prefixes
version Prints the version of etcdctl
lease grant Creates leases
lease revoke Revokes leases
lease timetolive Get lease information
lease list List all active leases
lease keep-alive Keeps leases alive (renew)
member add Adds a member into the cluster
member remove Removes a member from the cluster
member update Updates a member in the cluster
member list Lists all members in the cluster
snapshot save Stores an etcd node backend snapshot to a given file
snapshot restore Restores an etcd member snapshot to an etcd directory
snapshot status Gets backend snapshot status of a given file
make-mirror Makes a mirror at the destination etcd cluster
migrate Migrates keys in a v2 store to a mvcc store
lock Acquires a named lock
elect Observes and participates in leader election
auth enable Enables authentication
auth disable Disables authentication
user add Adds a new user
user delete Deletes a user
user get Gets detailed information of a user
user list Lists all users
user passwd Changes password of user
user grant-role Grants a role to a user
user revoke-role Revokes a role from a user
role add Adds a new role
role delete Deletes a role
role get Gets detailed information of a role
role list Lists all roles
role grant-permission Grants a key to a role
role revoke-permission Revokes a key from a role
check perf Check the performance of the etcd cluster
help Help about any command
OPTIONS:
--cacert="" verify certificates of TLS-enabled secure servers using this CA bundle
--cert="" identify secure client using this TLS certificate file
--command-timeout=5s timeout for short running command (excluding dial timeout)
--debug[=false] enable client-side debug logging
--dial-timeout=2s dial timeout for client connections
-d, --discovery-srv="" domain name to query for SRV records describing cluster endpoints
--endpoints=[127.0.0.1:2379] gRPC endpoints
--hex[=false] print byte strings as hex encoded strings
--insecure-discovery[=true] accept insecure SRV records describing cluster endpoints
--insecure-skip-tls-verify[=false] skip server certificate verification
--insecure-transport[=true] disable transport security for client connections
--keepalive-time=2s keepalive time for client connections
--keepalive-timeout=6s keepalive timeout for client connections
--key="" identify secure client using this TLS key file
--user="" username[:password] for authentication (prompt if password is not supplied)
-w, --write-out="simple" set the output format (fields, json, protobuf, simple, table)
3. etcdctl 常用命令
3.1. 指定etcd集群
HOST_1=10.240.0.17
HOST_2=10.240.0.18
HOST_3=10.240.0.19
ENDPOINTS=$HOST_1:2379,$HOST_2:2379,$HOST_3:2379
etcdctl --endpoints=$ENDPOINTS member list
如果etcd设置了证书访问,则需要添加证书相关参数:
ETCDCTL_API=3 etcdctl --endpoints=$ENDPOINTS --cacert=<ca-file> --cert=<cert-file> --key=<key-file> <command>
参数说明如下:
--cacert="" verify certificates of TLS-enabled secure servers using this CA bundle
--cert="" identify secure client using this TLS certificate file
--key="" identify secure client using this TLS key file
--endpoints=[127.0.0.1:2379] gRPC endpoints
可以自定义alias命令
# alias 命令,避免每次需要输入证书参数
alias ectl='ETCDCTL_API=3 etcdctl --endpoints=$ENDPOINTS --cacert=<ca-file> --cert=<cert-file> --key=<key-file>'
# 直接使用别名执行命令
ectl <command>
3.2. 增删改查
1、增
etcdctl --endpoints=$ENDPOINTS put foo "Hello World!"
2、查
etcdctl --endpoints=$ENDPOINTS get foo
etcdctl --endpoints=$ENDPOINTS --write-out="json" get foo
基于相同前缀查找
etcdctl --endpoints=$ENDPOINTS put web1 value1
etcdctl --endpoints=$ENDPOINTS put web2 value2
etcdctl --endpoints=$ENDPOINTS put web3 value3
etcdctl --endpoints=$ENDPOINTS get web --prefix
列出所有的key
etcdctl --endpoints=$ENDPOINTS get / --prefix --keys-only
3、删
etcdctl --endpoints=$ENDPOINTS put key myvalue
etcdctl --endpoints=$ENDPOINTS del key
etcdctl --endpoints=$ENDPOINTS put k1 value1
etcdctl --endpoints=$ENDPOINTS put k2 value2
etcdctl --endpoints=$ENDPOINTS del k --prefix
3.3. 集群状态
集群状态主要是etcdctl endpoint status 和etcdctl endpoint health两条命令。
etcdctl --write-out=table --endpoints=$ENDPOINTS endpoint status
+------------------+------------------+---------+---------+-----------+-----------+------------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+------------------+------------------+---------+---------+-----------+-----------+------------+
| 10.240.0.17:2379 | 4917a7ab173fabe7 | 3.0.0 | 45 kB | true | 4 | 16726 |
| 10.240.0.18:2379 | 59796ba9cd1bcd72 | 3.0.0 | 45 kB | false | 4 | 16726 |
| 10.240.0.19:2379 | 94df724b66343e6c | 3.0.0 | 45 kB | false | 4 | 16726 |
+------------------+------------------+---------+---------+-----------+-----------+------------+
etcdctl --endpoints=$ENDPOINTS endpoint health
10.240.0.17:2379 is healthy: successfully committed proposal: took = 3.345431ms
10.240.0.19:2379 is healthy: successfully committed proposal: took = 3.767967ms
10.240.0.18:2379 is healthy: successfully committed proposal: took = 4.025451ms
3.4. 集群成员
跟集群成员相关的命令如下:
member add Adds a member into the cluster
member remove Removes a member from the cluster
member update Updates a member in the cluster
member list Lists all members in the cluster
例如 etcdctl member list列出集群成员的命令。
etcdctl --endpoints=http://172.16.5.4:12379 member list -w table
+-----------------+---------+-------+------------------------+-----------------------------------------------+
| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS |
+-----------------+---------+-------+------------------------+-----------------------------------------------+
| c856d92a82ba66a | started | etcd0 | http://172.16.5.4:2380 | http://172.16.5.4:2379,http://172.16.5.4:4001 |
+-----------------+---------+-------+------------------------+-----------------------------------------------+
4. etcdctl get
使用etcdctl {command} --help可以查看具体命令的帮助信息。
# etcdctl get --help
NAME:
get - Gets the key or a range of keys
USAGE:
etcdctl get [options] <key> [range_end]
OPTIONS:
--consistency="l" Linearizable(l) or Serializable(s)
--from-key[=false] Get keys that are greater than or equal to the given key using byte compare
--keys-only[=false] Get only the keys
--limit=0 Maximum number of results
--order="" Order of results; ASCEND or DESCEND (ASCEND by default)
--prefix[=false] Get keys with matching prefix
--print-value-only[=false] Only write values when using the "simple" output format
--rev=0 Specify the kv revision
--sort-by="" Sort target; CREATE, KEY, MODIFY, VALUE, or VERSION
GLOBAL OPTIONS:
--cacert="" verify certificates of TLS-enabled secure servers using this CA bundle
--cert="" identify secure client using this TLS certificate file
--command-timeout=5s timeout for short running command (excluding dial timeout)
--debug[=false] enable client-side debug logging
--dial-timeout=2s dial timeout for client connections
--endpoints=[127.0.0.1:2379] gRPC endpoints
--hex[=false] print byte strings as hex encoded strings
--insecure-skip-tls-verify[=false] skip server certificate verification
--insecure-transport[=true] disable transport security for client connections
--key="" identify secure client using this TLS key file
--user="" username[:password] for authentication (prompt if password is not supplied)
-w, --write-out="simple" set the output format (fields, json, protobuf, simple, table)
文章参考:
12.4.2 - etcdctl-V2
1. etcdctl介绍
etcdctl是一个命令行的客户端,它提供了一下简洁的命令,可理解为命令工具集,可以方便我们在对服务进行测试或者手动修改数据库内容。etcdctl与其他xxxctl的命令原理及操作类似(例如kubectl,systemctl)。
用法:etcdctl [global options] command [command options][args...]
2. Etcd常用命令
2.1. 数据库操作命令
etcd 在键的组织上采用了层次化的空间结构(类似于文件系统中目录的概念),数据库操作围绕对键值和目录的 CRUD [增删改查](符合 REST 风格的一套操作:Create, Read, Update, Delete)完整生命周期的管理。
具体的命令选项参数可以通过 etcdctl command --help来获取相关帮助。
2.1.1. 对象为键值
-
set[增:无论是否存在]:etcdctl set key value
-
mk[增:必须不存在]:etcdctl mk key value
-
rm[删]:etcdctl rm key
-
update[改]:etcdctl update key value
-
get[查]:etcdctl get key
2.1.2. 对象为目录
-
setdir[增:无论是否存在]:etcdctl setdir dir
-
mkdir[增:必须不存在]: etcdctl mkdir dir
-
rmdir[删]:etcdctl rmdir dir
-
updatedir[改]:etcdctl updatedir dir
-
ls[查]:etcdclt ls
2.2. 非数据库操作命令
-
backup[备份 etcd 的数据]
etcdctl backup
-
watch[监测一个键值的变化,一旦键值发生更新,就会输出最新的值并退出]
etcdctl watch key
-
exec-watch[监测一个键值的变化,一旦键值发生更新,就执行给定命令]
etcdctl exec-watch key --sh -c "ls"
-
member[通过 list、add、remove、update 命令列出、添加、删除 、更新etcd 实例到 etcd 集群中]
etcdctl member list;etcdctl member add 实例;etcdctl member remove 实例;etcdctl member update 实例。
-
etcdctl cluster-health[检查集群健康状态]
2.3. 常用配置参数
设置配置文件,默认为/etc/etcd/etcd.conf。
| 配置参数 | 参数说明 |
|---|---|
| 配置参数 | 参数说明 |
| -name | 节点名称 |
| -data-dir | 保存日志和快照的目录,默认为当前工作目录,指定节点的数据存储目录 |
| -addr | 公布的ip地址和端口。 默认为127.0.0.1:2379 |
| -bind-addr | 用于客户端连接的监听地址,默认为-addr配置 |
| -peers | 集群成员逗号分隔的列表,例如 127.0.0.1:2380,127.0.0.1:2381 |
| -peer-addr | 集群服务通讯的公布的IP地址,默认为 127.0.0.1:2380. |
| -peer-bind-addr | 集群服务通讯的监听地址,默认为-peer-addr配置 |
| -wal-dir | 指定节点的was文件的存储目录,若指定了该参数,wal文件会和其他数据文件分开存储 |
| -listen-client-urls | |
| -listen-peer-urls | 监听URL,用于与其他节点通讯 |
| -initial-advertise-peer-urls | 告知集群其他节点url. |
| -advertise-client-urls | 告知客户端url, 也就是服务的url |
| -initial-cluster-token | 集群的ID |
| -initial-cluster | 集群中所有节点 |
| -initial-cluster-state | -initial-cluster-state=new 表示从无到有搭建etcd集群 |
| -discovery-srv | 用于DNS动态服务发现,指定DNS SRV域名 |
| -discovery | 用于etcd动态发现,指定etcd发现服务的URL [https://discovery.etcd.io/],用环境变量表示 |
12.5 - Etcd访问控制
1. ETCD资源类型
There are three types of resources in etcd
permission resources: users and roles in the user storekey-value resources: key-value pairs in the key-value storesettings resources: security settings, auth settings, and dynamic etcd cluster settings (election/heartbeat)
2. 权限资源
Users:user用来设置身份认证(user:passwd),一个用户可以拥有多个角色,每个角色被分配一定的权限(只读、只写、可读写),用户分为root用户和非root用户。
Roles:角色用来关联权限,角色主要三类:root角色。默认创建root用户时即创建了root角色,该角色拥有所有权限;guest角色,默认自动创建,主要用于非认证使用。普通角色,由root用户创建角色,并分配指定权限。
注意:如果没有指定任何验证方式,即没显示指定以什么用户进行访问,那么默认会设定为 guest 角色。默认情况下 guest 也是具有全局访问权限的。如果不希望未授权就获取或修改etcd的数据,则可收回guest角色的权限或删除该角色,etcdctl role revoke 。
Permissions:权限分为只读、只写、可读写三种权限,权限即对指定目录或key的读写权限。
3. ETCD访问控制
3.1. 访问控制相关命令
NAME:
etcdctl - A simple command line client for etcd.
USAGE:
etcdctl [global options] command [command options] [arguments...]
VERSION:
2.2.0
COMMANDS:
user user add, grant and revoke subcommands
role role add, grant and revoke subcommands
auth overall auth controls
GLOBAL OPTIONS:
--peers, -C a comma-delimited list of machine addresses in the cluster (default: "http://127.0.0.1:4001,http://127.0.0.1:2379")
--endpoint a comma-delimited list of machine addresses in the cluster (default: "http://127.0.0.1:4001,http://127.0.0.1:2379")
--cert-file identify HTTPS client using this SSL certificate file
--key-file identify HTTPS client using this SSL key file
--ca-file verify certificates of HTTPS-enabled servers using this CA bundle
--username, -u provide username[:password] and prompt if password is not supplied.
--timeout '1s' connection timeout per request
3.2. user相关命令
[root@localhost etcd]# etcdctl user --help
NAME:
etcdctl user - user add, grant and revoke subcommands
USAGE:
etcdctl user command [command options] [arguments...]
COMMANDS:
add add a new user for the etcd cluster
get get details for a user
list list all current users
remove remove a user for the etcd cluster
grant grant roles to an etcd user
revoke revoke roles for an etcd user
passwd change password for a user
help, h Shows a list of commands or help for one command
OPTIONS:
--help, -h show help
3.2.1. 添加root用户并设置密码
etcdctl --endpoints http://172.16.22.36:2379 user add root
3.2.2. 添加非root用户并设置密码
etcdctl --endpoints http://172.16.22.36:2379 --username root:123 user add huwh
3.2.3. 查看当前所有用户
etcdctl --endpoints http://172.16.22.36:2379 --username root:123 user list
3.2.4. 将用户添加到对应角色
etcdctl --endpoints http://172.16.22.36:2379 --username root:123 user grant --roles test1 phpor
3.2.5. 查看用户拥有哪些角色
etcdctl --endpoints http://172.16.22.36:2379 --username root:123 user get phpor
3.3. role相关命令
[root@localhost etcd]# etcdctl role --help
NAME:
etcdctl role - role add, grant and revoke subcommands
USAGE:
etcdctl role command [command options] [arguments...]
COMMANDS:
add add a new role for the etcd cluster
get get details for a role
list list all roles
remove remove a role from the etcd cluster
grant grant path matches to an etcd role
revoke revoke path matches for an etcd role
help, h Shows a list of commands or help for one command
OPTIONS:
--help, -h show help
3.3.1. 添加角色
etcdctl --endpoints http://172.16.22.36:2379 --username root:2379 role add test1
3.3.2. 查看所有角色
etcdctl --endpoints http://172.16.22.36:2379 --username root:123 role list
3.3.3. 给角色分配权限
[root@localhost etcd]# etcdctl role grant --help
NAME:
grant - grant path matches to an etcd role
USAGE:
command grant [command options] [arguments...]
OPTIONS:
--path Path granted for the role to access
--read Grant read-only access
--write Grant write-only access
--readwrite Grant read-write access
1、只包含目录 etcdctl --endpoints http://172.16.22.36:2379 --username root:123 role grant --readwrite --path /test1 test1
2、包括目录和子目录或文件 etcdctl --endpoints http://172.16.22.36:2379 --username root:123 role grant --readwrite --path /test1/* test1
3.3.4. 查看角色所拥有的权限
etcdctl --endpoints http://172.16.22.36:2379 --username root:2379 role get test1
3.4. auth相关操作
[root@localhost etcd]# etcdctl auth --help
NAME:
etcdctl auth - overall auth controls
USAGE:
etcdctl auth command [command options] [arguments...]
COMMANDS:
enable enable auth access controls
disable disable auth access controls
help, h Shows a list of commands or help for one command
OPTIONS:
--help, -h show help
3.4.1. 开启认证
etcdctl --endpoints http://172.16.22.36:2379 auth enable
4. 访问控制设置步骤
| 顺序 | 步骤 | 命令 |
|---|---|---|
| 1 | 添加root用户 | etcdctl --endpoints http:// |
| 2 | 开启认证 | etcdctl --endpoints http:// |
| 3 | 添加非root用户 | etcdctl --endpoints http:// |
| 4 | 添加角色 | etcdctl --endpoints http:// |
| 5 | 给角色授权(只读、只写、可读写) | etcdctl --endpoints http:// |
| 6 | 给用户分配角色(即分配了角色对应的权限) | etcdctl --endpoints http:// |
5. 访问认证的API调用
更多参考
12.6 - Etcd启动配置参数
1. Etcd配置参数
/ # etcd --help
usage: etcd [flags]
start an etcd server
etcd --version
show the version of etcd
etcd -h | --help
show the help information about etcd
etcd --config-file
path to the server configuration file
etcd gateway
run the stateless pass-through etcd TCP connection forwarding proxy
etcd grpc-proxy
run the stateless etcd v3 gRPC L7 reverse proxy
1.1. member flags
member flags:
--name 'default'
human-readable name for this member.
--data-dir '${name}.etcd'
path to the data directory.
--wal-dir ''
path to the dedicated wal directory.
--snapshot-count '100000'
number of committed transactions to trigger a snapshot to disk.
--heartbeat-interval '100'
time (in milliseconds) of a heartbeat interval.
--election-timeout '1000'
time (in milliseconds) for an election to timeout. See tuning documentation for details.
--initial-election-tick-advance 'true'
whether to fast-forward initial election ticks on boot for faster election.
--listen-peer-urls 'http://localhost:2380'
list of URLs to listen on for peer traffic.
--listen-client-urls 'http://localhost:2379'
list of URLs to listen on for client traffic.
--max-snapshots '5'
maximum number of snapshot files to retain (0 is unlimited).
--max-wals '5'
maximum number of wal files to retain (0 is unlimited).
--cors ''
comma-separated whitelist of origins for CORS (cross-origin resource sharing).
--quota-backend-bytes '0'
raise alarms when backend size exceeds the given quota (0 defaults to low space quota).
--max-txn-ops '128'
maximum number of operations permitted in a transaction.
--max-request-bytes '1572864'
maximum client request size in bytes the server will accept.
--grpc-keepalive-min-time '5s'
minimum duration interval that a client should wait before pinging server.
--grpc-keepalive-interval '2h'
frequency duration of server-to-client ping to check if a connection is alive (0 to disable).
--grpc-keepalive-timeout '20s'
additional duration of wait before closing a non-responsive connection (0 to disable).
1.2. clustering flags
clustering flags:
--initial-advertise-peer-urls 'http://localhost:2380'
list of this member's peer URLs to advertise to the rest of the cluster.
--initial-cluster 'default=http://localhost:2380'
initial cluster configuration for bootstrapping.
--initial-cluster-state 'new'
initial cluster state ('new' or 'existing').
--initial-cluster-token 'etcd-cluster'
initial cluster token for the etcd cluster during bootstrap.
Specifying this can protect you from unintended cross-cluster interaction when running multiple clusters.
--advertise-client-urls 'http://localhost:2379'
list of this member's client URLs to advertise to the public.
The client URLs advertised should be accessible to machines that talk to etcd cluster. etcd client libraries parse these URLs to connect to the cluster.
--discovery ''
discovery URL used to bootstrap the cluster.
--discovery-fallback 'proxy'
expected behavior ('exit' or 'proxy') when discovery services fails.
"proxy" supports v2 API only.
--discovery-proxy ''
HTTP proxy to use for traffic to discovery service.
--discovery-srv ''
dns srv domain used to bootstrap the cluster.
--strict-reconfig-check 'true'
reject reconfiguration requests that would cause quorum loss.
--auto-compaction-retention '0'
auto compaction retention length. 0 means disable auto compaction.
--auto-compaction-mode 'periodic'
interpret 'auto-compaction-retention' one of: periodic|revision. 'periodic' for duration based retention, defaulting to hours if no time unit is provided (e.g. '5m'). 'revision' for revision number based retention.
--enable-v2 'true'
Accept etcd V2 client requests.
1.3. proxy flags
proxy flags:
"proxy" supports v2 API only.
--proxy 'off'
proxy mode setting ('off', 'readonly' or 'on').
--proxy-failure-wait 5000
time (in milliseconds) an endpoint will be held in a failed state.
--proxy-refresh-interval 30000
time (in milliseconds) of the endpoints refresh interval.
--proxy-dial-timeout 1000
time (in milliseconds) for a dial to timeout.
--proxy-write-timeout 5000
time (in milliseconds) for a write to timeout.
--proxy-read-timeout 0
time (in milliseconds) for a read to timeout.
1.4. security flags
security flags:
--ca-file '' [DEPRECATED]
path to the client server TLS CA file. '-ca-file ca.crt' could be replaced by '-trusted-ca-file ca.crt -client-cert-auth' and etcd will perform the same.
--cert-file ''
path to the client server TLS cert file.
--key-file ''
path to the client server TLS key file.
--client-cert-auth 'false'
enable client cert authentication.
--client-crl-file ''
path to the client certificate revocation list file.
--trusted-ca-file ''
path to the client server TLS trusted CA cert file.
--auto-tls 'false'
client TLS using generated certificates.
--peer-ca-file '' [DEPRECATED]
path to the peer server TLS CA file. '-peer-ca-file ca.crt' could be replaced by '-peer-trusted-ca-file ca.crt -peer-client-cert-auth' and etcd will perform the same.
--peer-cert-file ''
path to the peer server TLS cert file.
--peer-key-file ''
path to the peer server TLS key file.
--peer-client-cert-auth 'false'
enable peer client cert authentication.
--peer-trusted-ca-file ''
path to the peer server TLS trusted CA file.
--peer-auto-tls 'false'
peer TLS using self-generated certificates if --peer-key-file and --peer-cert-file are not provided.
--peer-crl-file ''
path to the peer certificate revocation list file.
1.5. logging flags
logging flags
--debug 'false'
enable debug-level logging for etcd.
--log-package-levels ''
specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').
--log-output 'default'
specify 'stdout' or 'stderr' to skip journald logging even when running under systemd.
1.6. unsafe flags
unsafe flags:
Please be CAUTIOUS when using unsafe flags because it will break the guarantees
given by the consensus protocol.
--force-new-cluster 'false'
force to create a new one-member cluster.
1.7. profiling flags
profiling flags:
--enable-pprof 'false'
Enable runtime profiling data via HTTP server. Address is at client URL + "/debug/pprof/"
--metrics 'basic'
Set level of detail for exported metrics, specify 'extensive' to include histogram metrics.
--listen-metrics-urls ''
List of URLs to listen on for metrics.
1.8. auth flags
auth flags:
--auth-token 'simple'
Specify a v3 authentication token type and its options ('simple' or 'jwt').
1.9. experimental flags
experimental flags:
--experimental-initial-corrupt-check 'false'
enable to check data corruption before serving any client/peer traffic.
--experimental-corrupt-check-time '0s'
duration of time between cluster corruption check passes.
--experimental-enable-v2v3 ''
serve v2 requests through the v3 backend under a given prefix.
12.7 - Etcd中的k8s数据
1. 读取数据key
使用以下命令列出所有的key。
ETCDCTL_API=3 etcdctl --endpoints=<etcd-ip-1>:2379,<etcd-ip-2>:2379,<etcd-ip-3>:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/apiserver-etcd-client.key --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt get / --prefix --keys-only
参数说明:
--cacert="" verify certificates of TLS-enabled secure servers using this CA bundle
--cert="" identify secure client using this TLS certificate file
--key="" identify secure client using this TLS key file
--endpoints=[127.0.0.1:2379] gRPC endpoints
可以使用alias来重命名etcdctl一串的命令
alias ectl='ETCDCTL_API=3 etcdctl --endpoints=<etcd-ip-1>:2379,<etcd-ip-2>:2379,<etcd-ip-3>:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/apiserver-etcd-client.key --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt'
2. 集群数据
2.1. node
/registry/minions/<node-ip-1>
/registry/minions/<node-ip-2>
/registry/minions/<node-ip-3>
其他信息:
/registry/leases/kube-node-lease/<node-ip-1>
/registry/leases/kube-node-lease/<node-ip-2>
/registry/leases/kube-node-lease/<node-ip-3>
/registry/masterleases/<node-ip-2>
/registry/masterleases/<node-ip-3>
3. k8s对象数据
k8s对象数据的格式
3.1. namespace
/registry/namespaces/default
/registry/namespaces/game
/registry/namespaces/kube-node-lease
/registry/namespaces/kube-public
/registry/namespaces/kube-system
3.2. namespace级别对象
/registry/{resource}/{namespace}/{resource_name}
以下以常见k8s对象为例:
# deployment
/registry/deployments/default/game-2048
/registry/deployments/kube-system/prometheus-operator
# replicasets
/registry/replicasets/default/game-2048-c7d589ccf
# pod
/registry/pods/default/game-2048-c7d589ccf-8lsbw
# statefulsets
/registry/statefulsets/kube-system/prometheus-k8s
# daemonsets
/registry/daemonsets/kube-system/kube-proxy
# secrets
/registry/secrets/default/default-token-tbfmb
# serviceaccounts
/registry/serviceaccounts/default/default
service
# service
/registry/services/specs/default/game-2048
# endpoints
/registry/services/endpoints/default/game-2048
4. 读取数据value
由于k8s默认etcd中的数据是通过protobuf格式存储,因此看到的key和value的值是一串字符串。
alias ectl='ETCDCTL_API=3 etcdctl --endpoints=
:2379, :2379, :2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/apiserver-etcd-client.key --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt'
# ectl get /registry/namespaces/test -w json |jq
{
"header": {
"cluster_id": 12113422651334595000,
"member_id": 8381627376898157000,
"revision": 12321629,
"raft_term": 20
},
"kvs": [
{
"key": "L3JlZ2lzdHJ5L25hbWVzcGFjZXMvdGVzdA==",
"create_revision": 11670741,
"mod_revision": 11670741,
"version": 1,
"value": "azhzAAoPCgJ2MRIJTmFtZXNwYWNlElwKQgoEdGVzdBIAGgAiACokYWM1YmJjOTQtNTkxZi0xMWVhLWJiOTQtNmM5MmJmM2I3NmI1MgA4AEIICJuf3fIFEAB6ABIMCgprdWJlcm5ldGVzGggKBkFjdGl2ZRoAIgA="
}
],
"count": 1
}
其中key可以通过base64解码出来
echo "L3JlZ2lzdHJ5L25hbWVzcGFjZXMvdGVzdA==" | base64 --decode
# output
/registry/namespaces/test
value是值可以通过安装etcdhelper工具解析出来。
alias ehelper='etcdhelper -key /etc/kubernetes/pki/apiserver-etcd-client.key -cert /etc/kubernetes/pki/apiserver-etcd-client.crt -cacert /etc/kubernetes/pki/etcd/ca.crt'
# ehelper get /registry/namespaces/test
/v1, Kind=Namespace
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "test",
"uid": "ac5bbc94-591f-11ea-bb94-6c92bf3b76b5",
"creationTimestamp": "2020-02-27T05:11:55Z"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
5. 注意事项
- 由于k8s的etcd数据为了性能考虑,默认通过
protobuf格式存储,不要通过手动的方式去修改或添加k8s数据。 - 不推荐使用json格式存储etcd数据,如果需要json格式,可以使用
--storage-media-type=application/json参数存储,参考:https://github.com/kubernetes/kubernetes/issues/44670
6. 快捷命令
由于etcdctl的命令需要添加很多认证参数和endpoints的参数,因此可以使用别名的方式来简化命令。
# etcdctl
alias ectl='ETCDCTL_API=3 etcdctl --endpoints=<etcd-ip-1>:2379,<etcd-ip-2>:2379,<etcd-ip-3>:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/apiserver-etcd-client.key --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt'
# etcdhelper
alias ehelper='etcdhelper -key /etc/kubernetes/pki/apiserver-etcd-client.key -cert /etc/kubernetes/pki/apiserver-etcd-client.crt -cacert /etc/kubernetes/pki/etcd/ca.crt'
6.1. etcdhelper的使用
etcdhelper文档参考:https://github.com/openshift/origin/tree/master/tools/etcdhelper
# 必要的认证参数
-key - points to master.etcd-client.key
-cert - points to master.etcd-client.crt
-cacert - points to ca.crt
# 命令操作参数
ls - list all keys starting with prefix
get - get the specific value of a key
dump - dump the entire contents of the etcd
示例
$ ehelper ls /registry/leases/
/registry/leases/kube-node-lease/<ip-1>
/registry/leases/kube-node-lease/<ip-2>
/registry/leases/kube-node-lease/<ip-3>
$ ehelper get <key>
7. RBAC
附RBAC相关的key。
clusterrolebindings
/registry/clusterrolebindings/cluster-admin
/registry/clusterrolebindings/flannel
/registry/clusterrolebindings/galaxy
/registry/clusterrolebindings/helm
/registry/clusterrolebindings/kube-state-metrics
/registry/clusterrolebindings/kubeadm:kubelet-bootstrap
/registry/clusterrolebindings/kubeadm:node-autoapprove-bootstrap
/registry/clusterrolebindings/kubeadm:node-autoapprove-certificate-rotation
/registry/clusterrolebindings/kubeadm:node-proxier
/registry/clusterrolebindings/lbcf-controller
/registry/clusterrolebindings/prometheus-k8s
/registry/clusterrolebindings/prometheus-operator
/registry/clusterrolebindings/system:aws-cloud-provider
/registry/clusterrolebindings/system:basic-user
/registry/clusterrolebindings/system:controller:attachdetach-controller
/registry/clusterrolebindings/system:controller:certificate-controller
/registry/clusterrolebindings/system:controller:clusterrole-aggregation-controller
/registry/clusterrolebindings/system:controller:cronjob-controller
/registry/clusterrolebindings/system:controller:daemon-set-controller
/registry/clusterrolebindings/system:controller:deployment-controller
/registry/clusterrolebindings/system:controller:disruption-controller
/registry/clusterrolebindings/system:controller:endpoint-controller
/registry/clusterrolebindings/system:controller:expand-controller
/registry/clusterrolebindings/system:controller:generic-garbage-collector
/registry/clusterrolebindings/system:controller:horizontal-pod-autoscaler
/registry/clusterrolebindings/system:controller:job-controller
/registry/clusterrolebindings/system:controller:namespace-controller
/registry/clusterrolebindings/system:controller:node-controller
/registry/clusterrolebindings/system:controller:persistent-volume-binder
/registry/clusterrolebindings/system:controller:pod-garbage-collector
/registry/clusterrolebindings/system:controller:pv-protection-controller
/registry/clusterrolebindings/system:controller:pvc-protection-controller
/registry/clusterrolebindings/system:controller:replicaset-controller
/registry/clusterrolebindings/system:controller:replication-controller
/registry/clusterrolebindings/system:controller:resourcequota-controller
/registry/clusterrolebindings/system:controller:route-controller
/registry/clusterrolebindings/system:controller:service-account-controller
/registry/clusterrolebindings/system:controller:service-controller
/registry/clusterrolebindings/system:controller:statefulset-controller
/registry/clusterrolebindings/system:controller:ttl-controller
/registry/clusterrolebindings/system:coredns
/registry/clusterrolebindings/system:discovery
/registry/clusterrolebindings/system:kube-controller-manager
/registry/clusterrolebindings/system:kube-dns
/registry/clusterrolebindings/system:kube-scheduler
/registry/clusterrolebindings/system:node
/registry/clusterrolebindings/system:node-proxier
/registry/clusterrolebindings/system:public-info-viewer
/registry/clusterrolebindings/system:volume-scheduler
clusterroles
/registry/clusterroles/admin
/registry/clusterroles/cluster-admin
/registry/clusterroles/edit
/registry/clusterroles/flannel
/registry/clusterroles/kube-state-metrics
/registry/clusterroles/lbcf-controller
/registry/clusterroles/prometheus-k8s
/registry/clusterroles/prometheus-operator
/registry/clusterroles/system:aggregate-to-admin
/registry/clusterroles/system:aggregate-to-edit
/registry/clusterroles/system:aggregate-to-view
/registry/clusterroles/system:auth-delegator
/registry/clusterroles/system:aws-cloud-provider
/registry/clusterroles/system:basic-user
/registry/clusterroles/system:certificates.k8s.io:certificatesigningrequests:nodeclient
/registry/clusterroles/system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
/registry/clusterroles/system:controller:attachdetach-controller
/registry/clusterroles/system:controller:certificate-controller
/registry/clusterroles/system:controller:clusterrole-aggregation-controller
/registry/clusterroles/system:controller:cronjob-controller
/registry/clusterroles/system:controller:daemon-set-controller
/registry/clusterroles/system:controller:deployment-controller
/registry/clusterroles/system:controller:disruption-controller
/registry/clusterroles/system:controller:endpoint-controller
/registry/clusterroles/system:controller:expand-controller
/registry/clusterroles/system:controller:generic-garbage-collector
/registry/clusterroles/system:controller:horizontal-pod-autoscaler
/registry/clusterroles/system:controller:job-controller
/registry/clusterroles/system:controller:namespace-controller
/registry/clusterroles/system:controller:node-controller
/registry/clusterroles/system:controller:persistent-volume-binder
/registry/clusterroles/system:controller:pod-garbage-collector
/registry/clusterroles/system:controller:pv-protection-controller
/registry/clusterroles/system:controller:pvc-protection-controller
/registry/clusterroles/system:controller:replicaset-controller
/registry/clusterroles/system:controller:replication-controller
/registry/clusterroles/system:controller:resourcequota-controller
/registry/clusterroles/system:controller:route-controller
/registry/clusterroles/system:controller:service-account-controller
/registry/clusterroles/system:controller:service-controller
/registry/clusterroles/system:controller:statefulset-controller
/registry/clusterroles/system:controller:ttl-controller
/registry/clusterroles/system:coredns
/registry/clusterroles/system:csi-external-attacher
/registry/clusterroles/system:csi-external-provisioner
/registry/clusterroles/system:discovery
/registry/clusterroles/system:heapster
/registry/clusterroles/system:kube-aggregator
/registry/clusterroles/system:kube-controller-manager
/registry/clusterroles/system:kube-dns
/registry/clusterroles/system:kube-scheduler
/registry/clusterroles/system:kubelet-api-admin
/registry/clusterroles/system:node
/registry/clusterroles/system:node-bootstrapper
/registry/clusterroles/system:node-problem-detector
/registry/clusterroles/system:node-proxier
/registry/clusterroles/system:persistent-volume-provisioner
/registry/clusterroles/system:public-info-viewer
/registry/clusterroles/system:volume-scheduler
/registry/clusterroles/view
rolebindings
/registry/rolebindings/kube-public/kubeadm:bootstrap-signer-clusterinfo
/registry/rolebindings/kube-public/system:controller:bootstrap-signer
/registry/rolebindings/kube-system/kube-proxy
/registry/rolebindings/kube-system/kube-state-metrics
/registry/rolebindings/kube-system/kubeadm:kubeadm-certs
/registry/rolebindings/kube-system/kubeadm:kubelet-config-1.14
/registry/rolebindings/kube-system/kubeadm:nodes-kubeadm-config
/registry/rolebindings/kube-system/system::extension-apiserver-authentication-reader
/registry/rolebindings/kube-system/system::leader-locking-kube-controller-manager
/registry/rolebindings/kube-system/system::leader-locking-kube-scheduler
/registry/rolebindings/kube-system/system:controller:bootstrap-signer
/registry/rolebindings/kube-system/system:controller:cloud-provider
/registry/rolebindings/kube-system/system:controller:token-cleaner
roles
/registry/roles/kube-public/kubeadm:bootstrap-signer-clusterinfo
/registry/roles/kube-public/system:controller:bootstrap-signer
/registry/roles/kube-system/extension-apiserver-authentication-reader
/registry/roles/kube-system/kube-proxy
/registry/roles/kube-system/kube-state-metrics-resizer
/registry/roles/kube-system/kubeadm:kubeadm-certs
/registry/roles/kube-system/kubeadm:kubelet-config-1.14
/registry/roles/kube-system/kubeadm:nodes-kubeadm-config
/registry/roles/kube-system/system::leader-locking-kube-controller-manager
/registry/roles/kube-system/system::leader-locking-kube-scheduler
/registry/roles/kube-system/system:controller:bootstrap-signer
/registry/roles/kube-system/system:controller:cloud-provider
/registry/roles/kube-system/system:controller:token-cleaner
参考:
12.8 - etcd-operator的使用
本文主要介绍etcd-operator的部署及使用
1. 部署RBAC
下载create_role.sh、cluster-role-binding-template.yaml、cluster-role-template.yaml
例如:
|-- cluster-role-binding-template.yaml
|-- cluster-role-template.yaml
|-- create_role.sh
# 部署rbac
kubectl create ns operator
bash create_role.sh --namespace=operator # namespace与etcd-operator的ns一致
示例:
bash create_role.sh --namespace=operator
+ ROLE_NAME=etcd-operator
+ ROLE_BINDING_NAME=etcd-operator
+ NAMESPACE=default
+ for i in '"$@"'
+ case $i in
+ NAMESPACE=operator
+ echo 'Creating role with ROLE_NAME=etcd-operator, NAMESPACE=operator'
Creating role with ROLE_NAME=etcd-operator, NAMESPACE=operator
+ sed -e 's/<ROLE_NAME>/etcd-operator/g' -e 's/<NAMESPACE>/operator/g' cluster-role-template.yaml
+ kubectl create -f -
clusterrole.rbac.authorization.k8s.io/etcd-operator created
+ echo 'Creating role binding with ROLE_NAME=etcd-operator, ROLE_BINDING_NAME=etcd-operator, NAMESPACE=operator'
Creating role binding with ROLE_NAME=etcd-operator, ROLE_BINDING_NAME=etcd-operator, NAMESPACE=operator
+ sed -e 's/<ROLE_NAME>/etcd-operator/g' -e 's/<ROLE_BINDING_NAME>/etcd-operator/g' -e 's/<NAMESPACE>/operator/g' cluster-role-binding-template.yaml
+ kubectl create -f -
clusterrolebinding.rbac.authorization.k8s.io/etcd-operator created
1.1. create_role.sh 脚本
create_role.sh有三个入参,可以指定--namespace参数,该参数与etcd-operator部署的namespace应一致。默认为default。
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
ETCD_OPERATOR_ROOT=$(dirname "${BASH_SOURCE}")/../..
print_usage() {
echo "$(basename "$0") - Create Kubernetes RBAC role and role bindings for etcd-operator
Usage: $(basename "$0") [options...]
Options:
--role-name=STRING Name of ClusterRole to create
(default=\"etcd-operator\", environment variable: ROLE_NAME)
--role-binding-name=STRING Name of ClusterRoleBinding to create
(default=\"etcd-operator\", environment variable: ROLE_BINDING_NAME)
--namespace=STRING namespace to create role and role binding in. Must already exist.
(default=\"default\", environment variable: NAMESPACE)
" >&2
}
ROLE_NAME="${ROLE_NAME:-etcd-operator}"
ROLE_BINDING_NAME="${ROLE_BINDING_NAME:-etcd-operator}"
NAMESPACE="${NAMESPACE:-default}"
for i in "$@"
do
case $i in
--role-name=*)
ROLE_NAME="${i#*=}"
;;
--role-binding-name=*)
ROLE_BINDING_NAME="${i#*=}"
;;
--namespace=*)
NAMESPACE="${i#*=}"
;;
-h|--help)
print_usage
exit 0
;;
*)
print_usage
exit 1
;;
esac
done
echo "Creating role with ROLE_NAME=${ROLE_NAME}, NAMESPACE=${NAMESPACE}"
sed -e "s/<ROLE_NAME>/${ROLE_NAME}/g" \
-e "s/<NAMESPACE>/${NAMESPACE}/g" \
"cluster-role-template.yaml" | \
kubectl create -f -
echo "Creating role binding with ROLE_NAME=${ROLE_NAME}, ROLE_BINDING_NAME=${ROLE_BINDING_NAME}, NAMESPACE=${NAMESPACE}"
sed -e "s/<ROLE_NAME>/${ROLE_NAME}/g" \
-e "s/<ROLE_BINDING_NAME>/${ROLE_BINDING_NAME}/g" \
-e "s/<NAMESPACE>/${NAMESPACE}/g" \
"cluster-role-binding-template.yaml" | \
kubectl create -f -
1.2. cluster-role-binding-template.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: <ROLE_BINDING_NAME>
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: <ROLE_NAME>
subjects:
- kind: ServiceAccount
name: default
namespace: <NAMESPACE>
1.3. cluster-role-template.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: <ROLE_NAME>
rules:
- apiGroups:
- etcd.database.coreos.com
resources:
- etcdclusters
- etcdbackups
- etcdrestores
verbs:
- "*"
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- "*"
- apiGroups:
- ""
resources:
- pods
- services
- endpoints
- persistentvolumeclaims
- events
verbs:
- "*"
- apiGroups:
- apps
resources:
- deployments
verbs:
- "*"
# The following permissions can be removed if not using S3 backup and TLS
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
2. 部署etcd-operator
kubectl create -f etcd-operator.yaml
etcd-operator.yaml如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: etcd-operator
namespace: operator # 与rbac指定的ns一致
labels:
app: etcd-operator
spec:
replicas: 1
selector:
matchLabels:
app: etcd-operator
template:
metadata:
labels:
app: etcd-operator
spec:
containers:
- name: etcd-operator
image: registry.cn-shenzhen.aliyuncs.com/huweihuang/etcd-operator:v0.9.4
command:
- etcd-operator
# Uncomment to act for resources in all namespaces. More information in doc/user/clusterwide.md
- -cluster-wide
env:
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
查看CRD
#kubectl get customresourcedefinitions
NAME CREATED AT
etcdclusters.etcd.database.coreos.com 2020-08-01T13:02:18Z
查看etcd-operator的日志是否OK。
k logs -f etcd-operator-545df8d445-qpf6n -n operator
time="2020-08-01T13:02:18Z" level=info msg="etcd-operator Version: 0.9.4"
time="2020-08-01T13:02:18Z" level=info msg="Git SHA: c8a1c64"
time="2020-08-01T13:02:18Z" level=info msg="Go Version: go1.11.5"
time="2020-08-01T13:02:18Z" level=info msg="Go OS/Arch: linux/amd64"
time="2020-08-01T13:02:18Z" level=info msg="Event(v1.ObjectReference{Kind:\"Endpoints\", Namespace:\"operator\", Name:\"etcd-operator\", UID:\"7de38cff-1b7b-4bf2-9837-473fa66c9366\", APIVersion:\"v1\", ResourceVersion:\"41195930\", FieldPath:\"\"}): type: 'Normal' reason: 'LeaderElection' etcd-operator-545df8d445-qpf6n became leader"
以上内容表示etcd-operator运行正常。
3. 部署etcd集群
kubectl create -f etcd-cluster.yaml
当开启clusterwide则etcd集群与etcd-operator的ns可不同。
etcd-cluster.yaml
apiVersion: "etcd.database.coreos.com/v1beta2"
kind: "EtcdCluster"
metadata:
name: "default-etcd-cluster"
## Adding this annotation make this cluster managed by clusterwide operators
## namespaced operators ignore it
annotations:
etcd.database.coreos.com/scope: clusterwide
namespace: etcd # 此处的ns表示etcd集群部署在哪个ns下
spec:
size: 3
version: "v3.3.18"
repository: registry.cn-shenzhen.aliyuncs.com/huweihuang/etcd
pod:
busyboxImage: registry.cn-shenzhen.aliyuncs.com/huweihuang/busybox:1.28.0-glibc
查看集群部署结果
$ kgpo -n etcd
NAME READY STATUS RESTARTS AGE
default-etcd-cluster-b6phnpf8z8 1/1 Running 0 3m3s
default-etcd-cluster-hhgq4sbtgr 1/1 Running 0 109s
default-etcd-cluster-ttfh5fj92b 1/1 Running 0 2m29s
4. 访问etcd集群
查看service
$ kgsvc -n etcd
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default-etcd-cluster ClusterIP None <none> 2379/TCP,2380/TCP 5m37s
default-etcd-cluster-client ClusterIP 192.168.255.244 <none> 2379/TCP 5m37s
使用service地址访问
# 查看集群健康状态
$ ETCDCTL_API=3 etcdctl --endpoints 192.168.255.244:2379 endpoint health
192.168.255.244:2379 is healthy: successfully committed proposal: took = 1.96126ms
# 写数据
$ ETCDCTL_API=3 etcdctl --endpoints 192.168.255.244:2379 put foo bar
OK
# 读数据
$ ETCDCTL_API=3 etcdctl --endpoints 192.168.255.244:2379 get foo
foo
bar
5. 销毁etcd-operator
kubectl delete -f example/deployment.yaml
kubectl delete endpoints etcd-operator
kubectl delete crd etcdclusters.etcd.database.coreos.com
kubectl delete clusterrole etcd-operator
kubectl delete clusterrolebinding etcd-operator
参考:
- https://github.com/coreos/etcd-operator
- https://github.com/coreos/etcd-operator/blob/master/doc/user/install_guide.md
- https://github.com/coreos/etcd-operator/blob/master/doc/user/client_service.md
- https://github.com/coreos/etcd-operator/blob/master/doc/user/spec_examples.md
- https://github.com/coreos/etcd-operator/blob/master/doc/user/cluster_tls.md
13 - 多集群管理
13.1 - k8s多集群管理的思考
k8s多集群的思考
1. 为什么需要多集群
1、k8s单集群的承载能力有限。
Kubernetes v1.21 支持的最大节点数为 5000。 更具体地说,Kubernetes旨在适应满足以下所有标准的配置:
- 每个节点的 Pod 数量不超过 100
- 节点数不超过 5000
- Pod 总数不超过 150000
- 容器总数不超过 300000
参考:https://kubernetes.io/zh/docs/setup/best-practices/cluster-large/
且当节点数量较大时,会出现调度延迟,etcd读写延迟,apiserver负载高等问题,影响服务的正常创建。
2、分散集群服务风险。
全部服务都放在一个k8s集群中,当该集群出现异常,短期无法恢复的情况下,则影响全部服务和影响部署。为了避免机房等故障导致单集群异常,建议将k8s的master在分散在延迟较低的不同可用区部署,且在不同region部署多个k8s集群来进行集群级别的容灾。
3、当前混合云的使用方式和架构
当前部分公司会存在自建机房+不同云厂商的公有云从而来实现混部云的运营模式,那么自然会引入多集群管理的问题。
2. 多集群部署需要解决哪些问题
目标:让用户像使用单集群一样来使用多集群。
扩展集群的边界,服务的边界从单台物理机多个进程,发展到通过k8s集群来管理多台的物理机,再发展到管理多个的k8s集群。服务的边界从物理机发展到集群。
而多集群管理需要解决以下问题:
- 多集群服务的分发部署(deployment、daemonset等)
- 跨集群自动迁移与调度(当某个集群异常,服务可以在其他集群自动部署)
- 多集群服务发现,网络通信及负载均衡(service,ingress等)
而多集群服务的网络通信可以由Service mesh等来解决,本文不做重点讨论。
以上几个问题,可以先从k8s管理节点的思维进行分析
| 物理机视角 | 单集群视角 | 多集群视角 | |
|---|---|---|---|
| 进程的边界 | 物理机 | k8s集群 | 多集群 |
| 调度单元 | 进程或线程 | 容器或pod | 工作负载(deployment) |
| 服务的集合 | 工作负载(deployment) | 不同集群工作负载的集合体(workloadGroup) | |
| 服务发现 | service | 不同集群service的集合体 | |
| 服务迁移 | 工作负载(deployment)控制器 | 不同集群工作负载的集合体控制器 | |
| 服务调度 | nodename或者node selector | clustername或cluster selector | |
| pod的反亲和(相同deployment下的pod不调度在相同节点) | workload反亲和(相同workloadGroup分散在不同集群) |
2.1. 多集群工作负载的分发
单集群中k8s的调度单元是pod,即一个pod只能跑在一个节点上,一个节点可以运行多个pod,而不同节点上的一组pod是通过一个workload来控制和分发。类似这个逻辑,那么在多集群的视角下,多集群的调度单元是一个集群的workload,一个workload只能跑在一个集群中,一个集群可以运行多个workload。
那么就需要有一个控制器来管理不同k8s集群的相同workload。例如 workloadGroup。而该workloadGroup在不侵入k8s原生API的情况下,主要包含两个部分。
workloadGroup:
- 资源模板(Resource Template):服务的描述(workload)
- 分发策略(Propagaion Policy):服务分发的集群(即多个workload应该被分发到哪些集群运行)
workload描述的是什么服务运行在什么节点,workloadGroup描述的是什么服务运行在什么集群。
实现workloadGroup有两种方式:
- 一种是自定义API将workloadGroup中的Resource Template和Propagaion Policy合成在一个自定义的对象中,由用户直接指定该workloadGroup信息,从而将不同的workload分发到不同的集群中。
- 另一种方式是通过一个k8s载体来记录一个具体的workload对象,再由用户指定Propagaion Policy关联该workload对象,从而让控制器自动根据用户指定的Propagaion Policy将workload分发到不同的集群中。
2.2. 跨集群自动迁移与调度
单集群中k8s中通过workload中的nodeselector或者nodename以及亲和性来控制pod运行在哪个节点上。而多集群的视角下,则需要有一个控制器来实现集群级别的调度逻辑,例如clustername,cluster selector,cluster AntiAffinity,从而来自动控制workloadGroup下的workload分散在什么集群上。
3. 目前的多集群方案
3.1. Kubefed[Federation v2]
简介
基本思想
3.2. virtual kubelet
简介
基本思想
3.3. Karmada
简介
基本思想
参考:
- https://kubernetes.io/zh/docs/setup/best-practices/cluster-large/
- CoreOS 是如何将 Kubernetes 的性能提高 10 倍的?
- 当 K8s 集群达到万级规模,阿里巴巴如何解决系统各组件性能问题?
- https://github.com/kubernetes-sigs/kubefed
- https://jimmysong.io/kubernetes-handbook/practice/federation.html
- https://kubernetes.io/blog/2018/12/12/kubernetes-federation-evolution/
- https://zhuanlan.zhihu.com/p/355193315
13.2 - Virtual Kubelet
13.2.1 - Virtual Kubelet介绍
1. 简介
Virtual Kubelet是 Kubernetes kubelet 的一种实现,作为一种虚拟的kubelet用来连接k8s集群和其他平台的API。这允许k8s的节点由其他提供者(provider)提供支持,这些提供者例如serverless平台(ACI, AWS Fargate)、IoT Edge等。
一句话概括:Kubernetes API on top, programmable back。
2. 架构图
3. 功能
virtual kubelet提供一个可以自定义k8s node的依赖库。
目前支持的功能如下:
- 创建、删除、更新 pod
- 容器的日志、exec命令、metrics
- 获取pod、pod列表、pod status
- node的地址、容量、daemon
- 操作系统
- 自定义virtual network
4. Providers
virtual kubelet提供一个插件式的provider接口,让开发者可以自定义实现传统kubelet的功能。自定义的provider可以用自己的配置文件和环境参数。
自定义的provider必须提供以下功能:
- 提供pod、容器、资源的生命周期管理的功能
- 符合virtual kubelet提供的API
- 不直接访问k8s apiserver,定义获取数据的回调机制,例如configmap、secrets
开源的provider
5. 自定义provider
创建自定义provider的目录。
git clone https://github.com/virtual-kubelet/virtual-kubelet
cd virtual-kubelet
mkdir providers/my-provider
5.1. PodLifecylceHandler
当pod被k8s创建、更新、删除时,会调用以下方法。
type PodLifecycleHandler interface {
// CreatePod takes a Kubernetes Pod and deploys it within the provider.
CreatePod(ctx context.Context, pod *corev1.Pod) error
// UpdatePod takes a Kubernetes Pod and updates it within the provider.
UpdatePod(ctx context.Context, pod *corev1.Pod) error
// DeletePod takes a Kubernetes Pod and deletes it from the provider.
DeletePod(ctx context.Context, pod *corev1.Pod) error
// GetPod retrieves a pod by name from the provider (can be cached).
GetPod(ctx context.Context, namespace, name string) (*corev1.Pod, error)
// GetPodStatus retrieves the status of a pod by name from the provider.
GetPodStatus(ctx context.Context, namespace, name string) (*corev1.PodStatus, error)
// GetPods retrieves a list of all pods running on the provider (can be cached).
GetPods(context.Context) ([]*corev1.Pod, error)
}
PodLifecycleHandler是被PodController来调用,来管理被分配到node上的pod。
pc, _ := node.NewPodController(podControllerConfig) // <-- instatiates the pod controller
pc.Run(ctx) // <-- starts watching for pods to be scheduled on the node
5.2. PodNotifier(optional)
PodNotifier是可选实现,该接口主要用来通知virtual kubelet的pod状态变化。如果没有实现该接口,virtual-kubelet会定期检查所有pod的状态。
type PodNotifier interface {
// NotifyPods instructs the notifier to call the passed in function when
// the pod status changes.
//
// NotifyPods should not block callers.
NotifyPods(context.Context, func(*corev1.Pod))
}
5.3. NodeProvider
NodeProvider用来通知virtual-kubelet关于node状态的变化,virtual-kubelet会定期检查node是状态并相应地更新k8s。
type NodeProvider interface {
// Ping checks if the node is still active.
// This is intended to be lightweight as it will be called periodically as a
// heartbeat to keep the node marked as ready in Kubernetes.
Ping(context.Context) error
// NotifyNodeStatus is used to asynchronously monitor the node.
// The passed in callback should be called any time there is a change to the
// node's status.
// This will generally trigger a call to the Kubernetes API server to update
// the status.
//
// NotifyNodeStatus should not block callers.
NotifyNodeStatus(ctx context.Context, cb func(*corev1.Node))
}
NodeProvider是被NodeController调用,来管理k8s中的node对象。
nc, _ := node.NewNodeController(nodeProvider, nodeSpec) // <-- instantiate a node controller from a node provider and a kubernetes node spec
nc.Run(ctx) // <-- creates the node in kubernetes and starts up he controller
5.4. 测试
进入到项目根目录
make test
5.5. 示例代码
-
Azure Container Instances Provider
https://github.com/virtual-kubelet/azure-aci/blob/master/aci.go#L541
-
Alibaba Cloud ECI Provider
https://github.com/virtual-kubelet/alibabacloud-eci/blob/master/eci.go#L177
-
AWS Fargate Provider
https://github.com/virtual-kubelet/aws-fargate/blob/master/provider.go#L110
参考:
13.2.2 - Virtual Kubelet命令
virtual-kubelet --help
#./virtual-kubelet --help
virtual-kubelet implements the Kubelet interface with a pluggable
backend implementation allowing users to create kubernetes nodes without running the kubelet.
This allows users to schedule kubernetes workloads on nodes that aren't running Kubernetes.
Usage:
virtual-kubelet [flags]
virtual-kubelet [command]
Available Commands:
help Help about any command
providers Show the list of supported providers
version Show the version of the program
Flags:
--cluster-domain string kubernetes cluster-domain (default is 'cluster.local') (default "cluster.local")
--disable-taint disable the virtual-kubelet node taint
--enable-node-lease use node leases (1.13) for node heartbeats
--full-resync-period duration how often to perform a full resync of pods between kubernetes and the provider (default 1m0s)
-h, --help help for virtual-kubelet
--klog.alsologtostderr log to standard error as well as files
--klog.log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--klog.log_dir string If non-empty, write log files in this directory
--klog.log_file string If non-empty, use this log file
--klog.log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
--klog.logtostderr log to standard error instead of files (default true)
--klog.skip_headers If true, avoid header prefixes in the log messages
--klog.skip_log_headers If true, avoid headers when opening log files
--klog.stderrthreshold severity logs at or above this threshold go to stderr (default 2)
--klog.v Level number for the log level verbosity
--klog.vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
--kubeconfig string kube config file to use for connecting to the Kubernetes API server (default "/root/.kube/config")
--log-level string set the log level, e.g. "debug", "info", "warn", "error" (default "info")
--metrics-addr string address to listen for metrics/stats requests (default ":10255")
--namespace string kubernetes namespace (default is 'all')
--nodename string kubernetes node name (default "virtual-kubelet")
--os string Operating System (Linux/Windows) (default "Linux")
--pod-sync-workers int set the number of pod synchronization workers (default 10)
--provider string cloud provider
--provider-config string cloud provider configuration file
--startup-timeout duration How long to wait for the virtual-kubelet to start
--trace-exporter strings sets the tracing exporter to use, available exporters: [jaeger ocagent]
--trace-sample-rate string set probability of tracing samples
--trace-service-name string sets the name of the service used to register with the trace exporter (default "virtual-kubelet")
--trace-tag map add tags to include with traces in key=value form
Use "virtual-kubelet [command] --help" for more information about a command.
13.3 - Karmada
13.3.1 - Karmada介绍
本文由网络资源整理以作记录
简介
Karmada(Kubernetes Armada)是基于Kubernetes原生API的多集群管理系统。在多云和混合云场景下,Karmada提供可插拔,全自动化管理多集群应用,实现多云集中管理、高可用性、故障恢复和流量调度。
特性
- 基于K8s原生API的跨集群应用管理,用户可以方便快捷地将应用从单集群迁移到多集群。
- 中心式操作和管理Kubernetes集群。
- 跨集群应用可在多集群上自动扩展,故障转移和负载均衡。
- 高级的调度策略:区域,可用区,云提供商,集群亲和性/反亲和性。
- 支持创建分发用户自定义(CustomResourceDefinitions)资源。
框架结构

- ETCD:存储Karmada API对象。
- Karmada Scheduler:提供高级的多集群调度策略。
- Karmada Controller Manager: 包含多个Controller,Controller监听karmada对象并且与成员集群API server进行通信并创建成员集群的k8s对象。
- Cluster Controller:成员集群的生命周期管理与对象管理。
- Policy Controller:监听PropagationPolicy对象,创建ResourceBinding,配置资源分发策略。
- Binding Controller:监听ResourceBinding对象,并创建work对象响应资源清单。
- Execution Controller:监听work对象,并将资源分发到成员集群中。
资源分发流程
基本概念
- 资源模板(Resource Template):Karmada使用K8s原生API定义作为资源模板,便于快速对接K8s生态工具链。
- 分发策略(Propagaion Policy):Karmada提供独立的策略API,用来配置资源分发策略。
- 差异化策略(Override Policy):Karmada提供独立的差异化API,用来配置与集群相关的差异化配置。比如配置不同集群使用不同的镜像。
Karmada资源分发流程图:

参考:
14 - 边缘容器
14.1 - KubeEdge
14.1.1 - KubeEdge介绍
1. KubeEdge简介
KubeEdge是基于kubernetes之上将容器化应用的编排能力拓展到边缘主机或边缘设备,在云端和边缘端提供网络通信,应用部署、元数据同步等功能。同时支持MQTT协议,允许开发者在边缘端自定义接入边缘设备。
2. 功能
- 边缘计算:提供边缘节点自治能力,边缘节点数据处理能力。
- 便捷部署:开发者可以开发http或mqtt协议的应用,运行在云端和边缘端。
- k8s原生支持:可以通过k8s管理和监控边缘设备和边缘节点。
- 丰富的应用类型:可以在边缘端部署机器学习、图片识别、事件处理等应用。
3. 组件
3.1. 云端
-
CloudHub:一个web socket服务器,负责监听云端的更新、缓存及向
EdgeHub发送消息。 -
EdgeController:一个扩展的k8s控制器,负责管理边缘节点和pod元数据,同步边缘节点的数据,是
k8s-apiserver与EdgeCore的通信桥梁。 -
DeviceController:一个扩展的k8s控制器,负责管理节点设备,同步云端和边缘端的设备元数据和状态。
3.2. 边缘端
- EdgeHub:一个web socket客户端,负责云端与边缘端的信息交互,其中包括将云端的资源变更同步到边缘端及边缘端的状态变化同步到云端。
- Edged:运行在边缘节点,管理容器化应用的agent,负责pod生命周期的管理,类似kubelet。
- EventBus:一个MQTT客户端,与MQTT服务端交互,提供发布/订阅的能力。
- ServiceBus:一个HTTP客户端,与HTTP服务端交互。为云组件提供HTTP客户端功能,以访问在边缘运行的HTTP服务器。
- DeviceTwin:负责存储设备状态并同步设备状态到云端,同时提供应用的接口查询。
- MetaManager:
edged和edgehub之间的消息处理器,负责向轻量数据库(SQLite)存储或查询元数据。
4. 架构图

参考:
14.1.2 - KubeEdge源码分析
14.1.2.1 - Kubeedge之cloudcore 源码分析
kubeedge源码分析之cloudcore
本文源码分析基于kubeedge v1.1.0
本文主要分析cloudcore中CloudCoreCommand的基本流程,具体的cloudhub、edgecontroller、devicecontroller模块的实现逻辑待后续单独文章分析。
目录结构:
cloud/cmd/cloudcore
cloudcore
├── app
│ ├── options
│ │ └── options.go
│ └── server.go # NewCloudCoreCommand、registerModules
└── cloudcore.go # main函数
cloudcore部分包含以下模块:
- cloudhub
- edgecontroller
- devicecontroller
1. main函数
kubeedge的代码采用cobra命令框架,代码风格与k8s源码风格类似。cmd目录主要为cobra command的基本内容及参数解析,pkg目录包含具体的实现逻辑。
cloud/cmd/cloudcore/cloudcore.go
func main() {
command := app.NewCloudCoreCommand()
logs.InitLogs()
defer logs.FlushLogs()
if err := command.Execute(); err != nil {
os.Exit(1)
}
}
2. NewCloudCoreCommand
NewCloudCoreCommand为cobra command的构造函数,该类函数一般包含以下部分:
- 构造option
- 添加Flags
- 运行Run函数(核心)
cloud/cmd/cloudcore/app/server.go
func NewCloudCoreCommand() *cobra.Command {
opts := options.NewCloudCoreOptions()
cmd := &cobra.Command{
Use: "cloudcore",
Long: `CloudCore is the core cloud part of KubeEdge, which contains three modules: cloudhub,
edgecontroller, and devicecontroller. Cloudhub is a web server responsible for watching changes at the cloud side,
caching and sending messages to EdgeHub. EdgeController is an extended kubernetes controller which manages
edge nodes and pods metadata so that the data can be targeted to a specific edge node. DeviceController is an extended
kubernetes controller which manages devices so that the device metadata/status date can be synced between edge and cloud.`,
Run: func(cmd *cobra.Command, args []string) {
verflag.PrintAndExitIfRequested()
flag.PrintFlags(cmd.Flags())
// To help debugging, immediately log version
klog.Infof("Version: %+v", version.Get())
registerModules()
// start all modules
core.Run()
},
}
fs := cmd.Flags()
namedFs := opts.Flags()
verflag.AddFlags(namedFs.FlagSet("global"))
globalflag.AddGlobalFlags(namedFs.FlagSet("global"), cmd.Name())
for _, f := range namedFs.FlagSets {
fs.AddFlagSet(f)
}
usageFmt := "Usage:\n %s\n"
cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
cmd.SetUsageFunc(func(cmd *cobra.Command) error {
fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine())
cliflag.PrintSections(cmd.OutOrStderr(), namedFs, cols)
return nil
})
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine())
cliflag.PrintSections(cmd.OutOrStdout(), namedFs, cols)
})
return cmd
}
核心代码:
// 构造option
opts := options.NewCloudCoreOptions()
// 执行run函数
registerModules()
core.Run()
// 添加flags
fs.AddFlagSet(f)
3. registerModules
由于kubeedge的代码的大部分模块都采用了基于go-channel的消息通信框架Beehive(待后续单独文章分析),因此在各模块启动之前,需要将该模块注册到beehive的框架中。
其中cloudcore部分涉及的模块有:
- cloudhub
- edgecontroller
- devicecontroller
cloud/cmd/cloudcore/app/server.go
// registerModules register all the modules started in cloudcore
func registerModules() {
cloudhub.Register()
edgecontroller.Register()
devicecontroller.Register()
}
以下以cloudhub为例说明注册的过程。
cloudhub结构体主要包含:
- context:上下文,用来传递消息上下文
- stopChan:go channel通信
beehive框架中的模块需要实现Module接口,因此cloudhub也实现了该接口,其中核心方法为Start,用来启动相应模块的运行。
vendor/github.com/kubeedge/beehive/pkg/core/module.go
// Module interface
type Module interface {
Name() string
Group() string
Start(c *context.Context)
Cleanup()
}
以下为cloudHub结构体及注册函数。
cloud/pkg/cloudhub/cloudhub.go
type cloudHub struct {
context *context.Context
stopChan chan bool
}
func Register() {
core.Register(&cloudHub{})
}
具体的注册实现函数为core.Register,注册过程实际上就是将具体的模块结构体放入一个以模块名为key的map映射中,待后续调用。
vendor/github.com/kubeedge/beehive/pkg/core/module.go
// Register register module
func Register(m Module) {
if isModuleEnabled(m.Name()) {
modules[m.Name()] = m //将具体的模块结构体放入一个以模块名为key的map映射中
log.LOGGER.Info("module " + m.Name() + " registered")
} else {
disabledModules[m.Name()] = m
log.LOGGER.Info("module " + m.Name() +
" is not register, please check modules.yaml")
}
}
4. core.Run
CloudCoreCommand命令的Run函数实际上是运行beehive框架中注册的所有模块。
其中包括两部分逻辑:
- 启动运行所有注册模块
- 监听信号并做优雅清理
vendor/github.com/kubeedge/beehive/pkg/core/core.go
//Run starts the modules and in the end does module cleanup
func Run() {
//Address the module registration and start the core
StartModules()
// monitor system signal and shutdown gracefully
GracefulShutdown()
}
5. StartModules
StartModules获取context上下文,并以goroutine的方式运行所有已注册的模块。其中Start函数即每个模块的具体实现Module接口中的Start方法。不同模块各自定义自己的具体Start方法实现。
coreContext := context.GetContext(context.MsgCtxTypeChannel)
go module.Start(coreContext)
具体实现如下:
vendor/github.com/kubeedge/beehive/pkg/core/core.go
// StartModules starts modules that are registered
func StartModules() {
coreContext := context.GetContext(context.MsgCtxTypeChannel)
modules := GetModules()
for name, module := range modules {
//Init the module
coreContext.AddModule(name)
//Assemble typeChannels for send2Group
coreContext.AddModuleGroup(name, module.Group())
go module.Start(coreContext)
log.LOGGER.Info("starting module " + name)
}
}
6. GracefulShutdown
当收到相关信号,则执行各个模块实现的Cleanup方法。
vendor/github.com/kubeedge/beehive/pkg/core/core.go
// GracefulShutdown is if it gets the special signals it does modules cleanup
func GracefulShutdown() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM,
syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, syscall.SIGABRT)
select {
case s := <-c:
log.LOGGER.Info("got os signal " + s.String())
//Cleanup each modules
modules := GetModules()
for name, module := range modules {
log.LOGGER.Info("Cleanup module " + name)
module.Cleanup()
}
}
}
参考:
14.1.2.2 - Kubeedge之edgecore 源码分析
kubeedge源码分析之edgecore
本文源码分析基于kubeedge v1.1.0
本文主要分析edgecore中EdgeCoreCommand的基本流程,具体的edged、edgehub、metamanager等模块的实现逻辑待后续单独文章分析。
目录结构:
edgecore
├── app
│ ├── options
│ │ └── options.go
│ └── server.go # NewEdgeCoreCommand 、registerModules
└── edgecore.go # main
edgecore模块包含:
- edged
- edgehub
- metamanager
- eventbus
- servicebus
- devicetwin
- edgemesh
1. main函数
main入口函数,仍然是cobra命令框架格式。
edge/cmd/edgecore/edgecore.go
func main() {
command := app.NewEdgeCoreCommand()
logs.InitLogs()
defer logs.FlushLogs()
if err := command.Execute(); err != nil {
os.Exit(1)
}
}
2. NewEdgeCoreCommand
NewEdgeCoreCommand与NewCloudCoreCommand一样构造对应的cobra command结构体。
edge/cmd/edgecore/app/server.go
// NewEdgeCoreCommand create edgecore cmd
func NewEdgeCoreCommand() *cobra.Command {
opts := options.NewEdgeCoreOptions()
cmd := &cobra.Command{
Use: "edgecore",
Long: `Edgecore is the core edge part of KubeEdge, which contains six modules: devicetwin, edged,
edgehub, eventbus, metamanager, and servicebus. DeviceTwin is responsible for storing device status
and syncing device status to the cloud. It also provides query interfaces for applications. Edged is an
agent that runs on edge nodes and manages containerized applications and devices. Edgehub is a web socket
client responsible for interacting with Cloud Service for the edge computing (like Edge Controller as in the KubeEdge
Architecture). This includes syncing cloud-side resource updates to the edge, and reporting
edge-side host and device status changes to the cloud. EventBus is a MQTT client to interact with MQTT
servers (mosquito), offering publish and subscribe capabilities to other components. MetaManager
is the message processor between edged and edgehub. It is also responsible for storing/retrieving metadata
to/from a lightweight database (SQLite).ServiceBus is a HTTP client to interact with HTTP servers (REST),
offering HTTP client capabilities to components of cloud to reach HTTP servers running at edge. `,
Run: func(cmd *cobra.Command, args []string) {
verflag.PrintAndExitIfRequested()
flag.PrintFlags(cmd.Flags())
// To help debugging, immediately log version
klog.Infof("Version: %+v", version.Get())
registerModules()
// start all modules
core.Run()
},
}
fs := cmd.Flags()
namedFs := opts.Flags()
verflag.AddFlags(namedFs.FlagSet("global"))
globalflag.AddGlobalFlags(namedFs.FlagSet("global"), cmd.Name())
for _, f := range namedFs.FlagSets {
fs.AddFlagSet(f)
}
usageFmt := "Usage:\n %s\n"
cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
cmd.SetUsageFunc(func(cmd *cobra.Command) error {
fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine())
cliflag.PrintSections(cmd.OutOrStderr(), namedFs, cols)
return nil
})
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine())
cliflag.PrintSections(cmd.OutOrStdout(), namedFs, cols)
})
return cmd
}
核心代码:
opts := options.NewEdgeCoreOptions()
registerModules()
core.Run()
3. registerModules
edgecore仍然采用Beehive通信框架,模块调用前先注册对应的模块。具体参考cloudcore.registerModules处的分析,此处不再展开分析注册流程。此处注册的是edgecore中涉及的组件。
edge/cmd/edgecore/app/server.go
// registerModules register all the modules started in edgecore
func registerModules() {
devicetwin.Register()
edged.Register()
edgehub.Register()
eventbus.Register()
edgemesh.Register()
metamanager.Register()
servicebus.Register()
test.Register()
dbm.InitDBManager()
}
4. core.Run
core.Run与cloudcore.run处逻辑一致不再展开分析。
vendor/github.com/kubeedge/beehive/pkg/core/core.go
//Run starts the modules and in the end does module cleanup
func Run() {
//Address the module registration and start the core
StartModules()
// monitor system signal and shutdown gracefully
GracefulShutdown()
}
参考:
14.2 - OpenYurt
14.2.1 - OpenYurt部署
本文主要介绍部署openyurt组件到k8s集群中。
1. 给云端节点和边缘节点打标签
openyurt将k8s节点分为云端节点和边缘节点,云端节点主要运行一些云端的业务,边缘节点运行边缘业务。当与 apiserver 断开连接时,只有运行在边缘自治的节点上的Pod才不会被驱逐。通过打 openyurt.io/is-edge-worker 的标签的方式来区分,false表示云端节点,true表示边缘节点。
云端组件:
-
yurt-controller-manager
-
yurt-tunnel-server
边缘组件:
-
yurt-hub
-
yurt-tunnel-agent
1.1. openyurt.io/is-edge-worker节点标签
# 云端节点,值为false
kubectl label node us-west-1.192.168.0.87 openyurt.io/is-edge-worker=false
# 边缘节点,值为true
kubectl label node us-west-1.192.168.0.88 openyurt.io/is-edge-worker=true
1.2. 给边缘节点开启自治模式
kubectl annotate node us-west-1.192.168.0.88 node.beta.openyurt.io/autonomy=true
2. 安装准备
2.1. 调整k8s组件的配置
2.2. 部署tunnel-dns
wget https://raw.githubusercontent.com/openyurtio/openyurt/master/config/setup/yurt-tunnel-dns.yaml
kubectl apply -f yurt-tunnel-dns.yaml
获取clusterIP,作为kube-apiserver的专用nameserver地址。
kubectl -n kube-system get svc yurt-tunnel-dns -o=jsonpath='{.spec.clusterIP}'
3. 部署openyurt控制面
通过helm来部署控制面,所有helm charts都可以在openyurt-helm 仓库中找到。
快捷安装可参考脚本:helm-install-openyurt.sh
helm repo add openyurt https://openyurtio.github.io/openyurt-helm
3.1. yurt-app-manager
helm upgrade --install yurt-app-manager -n kube-system openyurt/yurt-app-manager
3.2. openyurt
在openyurt/openyurt中的组件包括:
- yurt-controller-manager: 防止apiserver在断开连接时驱逐运行在边缘节点上的pod
- yurt-tunnel-server: 在云端构建云边隧道
- yurt-tunnel-agent: 在边缘侧构建云边隧道
由于yurt-tunnel-server默认使用host模式,因此可能存在边缘端的agent无法访问云端的tunnel-server,需要为tunnel-server配置一个可访问的地址。
# 下载并解压
helm pull openyurt/openyurt --untar
# 修改tunnel相关配置
cd openyurt
vi values.yaml
# 示例:
yurtTunnelServer:
replicaCount: 1
tolerations: []
parameters:
certDnsNames: "<tunnel server的域名>"
tunnelAgentConnectPort: <tunnel server端口,默认为10262>
certIps: ""
yurtTunnelAgent:
replicaCount: 1
tolerations: []
parameters:
tunnelserverAddr: "<tunnel server的地址,包括端口>"
# install
helm install openyurt ./openyurt
4. 部署 Yurthub(edge)
在 yurt-controller-manager 启动并正常运行后,以静态 pod 的方式部署 Yurthub。
- 为 yurthub 创建全局配置(即RBAC, configmap)
wget https://raw.githubusercontent.com/openyurtio/openyurt/master/config/setup/yurthub-cfg.yaml
kubectl apply -f yurthub-cfg.yaml
- 在边缘节点以static pod方式创建yurthub
mkdir -p /etc/kubernetes/manifests/
cd /etc/kubernetes/manifests/
wget https://raw.githubusercontent.com/openyurtio/openyurt/master/config/setup/yurthub.yaml
# 获取bootstrap token
kubeadm token create
# 假设 apiserver 的地址是 1.2.3.4:6443,bootstrap token 是 07401b.f395accd246ae52d
sed -i 's|__kubernetes_master_address__|1.2.3.4:6443|;
s|__bootstrap_token__|07401b.f395accd246ae52d|' /etc/kubernetes/manifests/yurthub.yaml
5. 重置 Kubelet
重置 kubelet 服务,让它通过 yurthub 访问apiserver。为 kubelet 服务创建一个新的 kubeconfig 文件来访问apiserver。
mkdir -p /var/lib/openyurt
cat << EOF > /var/lib/openyurt/kubelet.conf
apiVersion: v1
clusters:
- cluster:
server: http://127.0.0.1:10261
name: default-cluster
contexts:
- context:
cluster: default-cluster
namespace: default
user: default-auth
name: default-context
current-context: default-context
kind: Config
preferences: {}
EOF
修改/etc/systemd/system/kubelet.service.d/10-kubeadm.conf
sed -i "s|KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=\/etc\/kubernetes\/bootstrap-kubelet.conf\ --kubeconfig=\/etc\/kubernetes\/kubelet.conf|KUBELET_KUBECONFIG_ARGS=--kubeconfig=\/var\/lib\/openyurt\/kubelet.conf|g" \
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf
重启kubelet服务
systemctl daemon-reload && systemctl restart kubelet
6. yurthub部署脚本
根据以上部署步骤,整理部署脚本。需要修改脚本内容的master-addr和token字段。
#!/bin/bash
set -e
set -x
### install yurthub ###
mkdir -p /etc/kubernetes/manifests/
cd /etc/kubernetes/manifests/
wget https://raw.githubusercontent.com/openyurtio/openyurt/master/config/setup/yurthub.yaml
### 修改master和token字段
sed -i 's|__kubernetes_master_address__|<master-addr>:6443|;
s|__bootstrap_token__|<token>|' /etc/kubernetes/manifests/yurthub.yaml
mkdir -p /var/lib/openyurt
cat << EOF > /var/lib/openyurt/kubelet.conf
apiVersion: v1
clusters:
- cluster:
server: http://127.0.0.1:10261
name: default-cluster
contexts:
- context:
cluster: default-cluster
namespace: default
user: default-auth
name: default-context
current-context: default-context
kind: Config
preferences: {}
EOF
cp /etc/systemd/system/kubelet.service.d/10-kubeadm.conf /etc/systemd/system/kubelet.service.d/10-kubeadm.conf.bak
sed -i "s|KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=\/etc\/kubernetes\/bootstrap-kubelet.conf\ --kubeconfig=\/etc\/kubernetes\/kubelet.conf|KUBELET_KUBECONFIG_ARGS=--kubeconfig=\/var\/lib\/openyurt\/kubelet.conf|g" \
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf
systemctl daemon-reload && systemctl restart kubelet
参考:
14.2.2 - OpenYurt 安装相关Kubernetes配置调整
安装openyurt,为了适配边缘场景,需要对k8s组件进行调整。其中包括:
-
kube-apiserver
-
kube-controller-manager
-
kube-proxy
-
CoreDNS
1. kube-apiserver
为了实现云边通信,即用户可以正常使用kubectl exec/logs的功能来登录或查看边缘容器的信息。需要将kube-apiserver访问kubelet的地址调整为hostname优先。
$ vi /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
...
spec:
dnsPolicy: "None" # 1. dnsPolicy修改为None
dnsConfig: # 2. 增加dnsConfig配置
nameservers:
- 1.2.3.4 # 使用yurt-tunnel-dns service的clusterIP替换
searches:
- kube-system.svc.cluster.local
- svc.cluster.local
- cluster.local
options:
- name: ndots
value: "5"
containers:
- command:
- kube-apiserver
...
- --kubelet-preferred-address-types=Hostname,InternalIP,ExternalIP # 3. 把Hostname放在第一位
...
2. kube-controller-manager
禁用默认的 nodelifecycle 控制器,当节点断连时不驱逐pod。
nodelifecycle控制器主要用来根据node的status及lease的更新时间来决定是否要驱逐节点上的pod。为了让 yurt-controller-mamanger 能够正常工作,因此需要禁用controller的驱逐功能。
vim /etc/kubernetes/manifests/kube-controller-manager.yaml
# 在--controllers=*,bootstrapsigner,tokencleaner后面添加,-nodelifecycle
# 即参数为: --controllers=*,bootstrapsigner,tokencleaner,-nodelifecycle
# 如果kube-controller-manager是以static pod部署,修改yaml文件后会自动重启。
3. CoreDNS
将coredns从deployment部署改为daemonset部署。
将deployment的coredns副本数调整为0。
kubectl scale --replicas=0 deployment/coredns -n kube-system
创建daemonset的coredns。
wget https://raw.githubusercontent.com/huweihuang/kubeadm-scripts/main/openyurt/yurt-tunnel/coredns.ds.yaml
kubectl apply -f
支持流量拓扑:
# 利用openyurt实现endpoint过滤
kubectl annotate svc kube-dns -n kube-system openyurt.io/topologyKeys='openyurt.io/nodepool'
4. kube-proxy
云边端场景下,边缘节点间很有可能无法互通,因此需要endpoints基于nodepool进行拓扑。直接将kube-proxy的kubeconfig配置删除,将apiserver请求经过yurthub即可解决服务拓扑问题。
kubectl edit cm -n kube-system kube-proxy
示例:
apiVersion: v1
data:
config.conf: |-
apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: 0.0.0.0
bindAddressHardFail: false
clientConnection:
acceptContentTypes: ""
burst: 0
contentType: ""
#kubeconfig: /var/lib/kube-proxy/kubeconfig.conf <-- 删除这个配置
qps: 0
clusterCIDR: 100.64.0.0/10
configSyncPeriod: 0s
// 省略
参考:
14.2.3 - OpenYurt源码分析
14.2.3.1 - OpenYurt之YurtHub源码分析
本文分析
yurthub源码,第一部分。本文以commit id:
180282663457080119a1bc6076cce20c922b5c50, 对应版本tag:v1.2.1的源码分析yurthub的实现逻辑。
yurthub是部署在每个边缘节点上用来实现边缘自治的组件。在云边通信正常的情况下实现apiserver的请求转发,断网的情况下通过本地的缓存数据保证节点上容器的正常运行。
基本架构图:

pkg包中yurthub代码目录结构;
yurthub
├── cachemanager # cache 管理器,
├── certificate # 证书token管理
├── filter
├── gc # GCManager
├── healthchecker # cloud apiserver 探火机制
├── kubernetes #
├── metrics
├── network # 网络iptables配置
├── otaupdate
├── poolcoordinator
├── proxy # 核心代码,反向代理机制,包括remote proxy和local proxy
├── server # yurthub server
├── storage # 本地存储的实现
├── tenant
├── transport
└── util
1. NewCmdStartYurtHub
openyurt的代码风格与k8s的一致,由cmd为入口,pkg为主要的实现逻辑。
以下是cmd的main函数。
func main() {
newRand := rand.New(rand.NewSource(time.Now().UnixNano()))
newRand.Seed(time.Now().UnixNano())
cmd := app.NewCmdStartYurtHub(server.SetupSignalContext())
cmd.Flags().AddGoFlagSet(flag.CommandLine)
if err := cmd.Execute(); err != nil {
panic(err)
}
}
main 函数主要创建 NewCmdStartYurtHub 对象。NewCmd的函数一般都包含以下的几个部分,运行顺序从上到下:
-
NewYurtHubOptions:创建option参数对象,主要用于flag参数解析到option的结构体。
-
yurtHubOptions.AddFlags(cmd.Flags()):添加AddFlags,设置flag参数信息。
-
yurtHubOptions.Validate():校验flag解析后的option的参数合法性。
-
yurtHubCfg, err := config.Complete(yurtHubOptions):将option的参数转换为config的对象。
-
Run(ctx, yurtHubCfg):基于config执行run函数,运行cmd的核心逻辑。
// NewCmdStartYurtHub creates a *cobra.Command object with default parameters
func NewCmdStartYurtHub(ctx context.Context) *cobra.Command {
yurtHubOptions := options.NewYurtHubOptions()
cmd := &cobra.Command{
Use: projectinfo.GetHubName(),
Short: "Launch " + projectinfo.GetHubName(),
Long: "Launch " + projectinfo.GetHubName(),
Run: func(cmd *cobra.Command, args []string) {
if yurtHubOptions.Version {
fmt.Printf("%s: %#v\n", projectinfo.GetHubName(), projectinfo.Get())
return
}
fmt.Printf("%s version: %#v\n", projectinfo.GetHubName(), projectinfo.Get())
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
klog.V(1).Infof("FLAG: --%s=%q", flag.Name, flag.Value)
})
if err := yurtHubOptions.Validate(); err != nil {
klog.Fatalf("validate options: %v", err)
}
yurtHubCfg, err := config.Complete(yurtHubOptions)
if err != nil {
klog.Fatalf("complete %s configuration error, %v", projectinfo.GetHubName(), err)
}
klog.Infof("%s cfg: %#+v", projectinfo.GetHubName(), yurtHubCfg)
if err := Run(ctx, yurtHubCfg); err != nil {
klog.Fatalf("run %s failed, %v", projectinfo.GetHubName(), err)
}
},
}
yurtHubOptions.AddFlags(cmd.Flags())
return cmd
}
以上flag、option、config的构建函数此处不做分析,以下分析run函数的逻辑。
2. Run(ctx, yurtHubCfg)
Run函数部分主要构建了几个manager,每个manager各司其职,负责对应的逻辑。有的manager在该函数中直接构建后执行manager.run的逻辑。有的则作为参数传入下一级函数中再执行manager.run函数。
主要包括以下的manager:
-
transportManager
-
cloudHealthChecker
-
restConfigMgr
-
cacheMgr
-
gcMgr
-
tenantMgr
-
NetworkMgr
每个manager的实现细节此处暂不做分析。
此处先贴一下完整源码,避免读者还需要去翻代码。
// Run runs the YurtHubConfiguration. This should never exit
func Run(ctx context.Context, cfg *config.YurtHubConfiguration) error {
defer cfg.CertManager.Stop()
trace := 1
klog.Infof("%d. new transport manager", trace)
// 构造NewTransportManager
transportManager, err := transport.NewTransportManager(cfg.CertManager, ctx.Done())
if err != nil {
return fmt.Errorf("could not new transport manager, %w", err)
}
trace++
klog.Infof("%d. prepare cloud kube clients", trace)
cloudClients, err := createClients(cfg.HeartbeatTimeoutSeconds, cfg.RemoteServers, cfg.CoordinatorServerURL, transportManager)
if err != nil {
return fmt.Errorf("failed to create cloud clients, %w", err)
}
trace++
var cloudHealthChecker healthchecker.MultipleBackendsHealthChecker
if cfg.WorkingMode == util.WorkingModeEdge {
klog.Infof("%d. create health checkers for remote servers and pool coordinator", trace)
cloudHealthChecker, err = healthchecker.NewCloudAPIServerHealthChecker(cfg, cloudClients, ctx.Done())
if err != nil {
return fmt.Errorf("could not new cloud health checker, %w", err)
}
} else {
klog.Infof("%d. disable health checker for node %s because it is a cloud node", trace, cfg.NodeName)
// In cloud mode, cloud health checker is not needed.
// This fake checker will always report that the cloud is healthy and pool coordinator is unhealthy.
cloudHealthChecker = healthchecker.NewFakeChecker(true, make(map[string]int))
}
trace++
klog.Infof("%d. new restConfig manager", trace)
restConfigMgr, err := hubrest.NewRestConfigManager(cfg.CertManager, cloudHealthChecker)
if err != nil {
return fmt.Errorf("could not new restConfig manager, %w", err)
}
trace++
var cacheMgr cachemanager.CacheManager
if cfg.WorkingMode == util.WorkingModeEdge {
klog.Infof("%d. new cache manager with storage wrapper and serializer manager", trace)
cacheMgr = cachemanager.NewCacheManager(cfg.StorageWrapper, cfg.SerializerManager, cfg.RESTMapperManager, cfg.SharedFactory)
} else {
klog.Infof("%d. disable cache manager for node %s because it is a cloud node", trace, cfg.NodeName)
}
trace++
if cfg.WorkingMode == util.WorkingModeEdge {
klog.Infof("%d. new gc manager for node %s, and gc frequency is a random time between %d min and %d min", trace, cfg.NodeName, cfg.GCFrequency, 3*cfg.GCFrequency)
gcMgr, err := gc.NewGCManager(cfg, restConfigMgr, ctx.Done())
if err != nil {
return fmt.Errorf("could not new gc manager, %w", err)
}
// 直接运行manager
gcMgr.Run()
} else {
klog.Infof("%d. disable gc manager for node %s because it is a cloud node", trace, cfg.NodeName)
}
trace++
klog.Infof("%d. new tenant sa manager", trace)
tenantMgr := tenant.New(cfg.TenantNs, cfg.SharedFactory, ctx.Done())
trace++
var coordinatorHealthCheckerGetter func() healthchecker.HealthChecker = getFakeCoordinatorHealthChecker
var coordinatorTransportManagerGetter func() transport.Interface = getFakeCoordinatorTransportManager
var coordinatorGetter func() poolcoordinator.Coordinator = getFakeCoordinator
if cfg.EnableCoordinator {
klog.Infof("%d. start to run coordinator", trace)
trace++
coordinatorInformerRegistryChan := make(chan struct{})
// coordinatorRun will register secret informer into sharedInformerFactory, and start a new goroutine to periodically check
// if certs has been got from cloud APIServer. It will close the coordinatorInformerRegistryChan if the secret channel has
// been registered into informer factory.
coordinatorHealthCheckerGetter, coordinatorTransportManagerGetter, coordinatorGetter = coordinatorRun(ctx, cfg, restConfigMgr, cloudHealthChecker, coordinatorInformerRegistryChan)
// wait for coordinator informer registry
klog.Infof("waiting for coordinator informer registry")
<-coordinatorInformerRegistryChan
klog.Infof("coordinator informer registry finished")
}
// Start the informer factory if all informers have been registered
cfg.SharedFactory.Start(ctx.Done())
cfg.YurtSharedFactory.Start(ctx.Done())
klog.Infof("%d. new reverse proxy handler for remote servers", trace)
// 将之前构造的manager作为参数构建yurtProxyHandler
yurtProxyHandler, err := proxy.NewYurtReverseProxyHandler(
cfg,
cacheMgr,
transportManager,
cloudHealthChecker,
tenantMgr,
coordinatorGetter,
coordinatorTransportManagerGetter,
coordinatorHealthCheckerGetter,
ctx.Done())
if err != nil {
return fmt.Errorf("could not create reverse proxy handler, %w", err)
}
trace++
if cfg.NetworkMgr != nil {
cfg.NetworkMgr.Run(ctx.Done())
}
klog.Infof("%d. new %s server and begin to serve", trace, projectinfo.GetHubName())
// 基于yurtProxyHandler运行一个http server.
if err := server.RunYurtHubServers(cfg, yurtProxyHandler, restConfigMgr, ctx.Done()); err != nil {
return fmt.Errorf("could not run hub servers, %w", err)
}
<-ctx.Done()
klog.Infof("hub agent exited")
return nil
}
除了上述的各种manager的构造及运行外,run函数中还构建了yurtProxyHandler,最终执行RunYurtHubServers运行一组不会退出的http server。以下先不对manager的实现做展开,而直接分析RunYurtHubServers的逻辑。RunYurtHubServers的代码在pkg包中。
3. RunYurtHubServers
RunYurtHubServers就是一个传统的http server的运行逻辑,主要包括几个不同类型的http server。http server的运行逻辑可以概括如下:
-
hubServerHandler := mux.NewRouter(): 新建路由创建handler
-
registerHandlers(hubServerHandler, cfg, rest): 注册路由
-
YurtHubServerServing.Serve:执行http server.Serve函数启动一个server服务。
http server分为两类:
-
yurthub http server: yurthub metrics, healthz的接口。
-
yurthub proxy server: 代理kube-apiserver的请求。
3.1. YurtHubServerServing
hubServerHandler := mux.NewRouter()
registerHandlers(hubServerHandler, cfg, rest)
// start yurthub http server for serving metrics, pprof.
if cfg.YurtHubServerServing != nil {
if err := cfg.YurtHubServerServing.Serve(hubServerHandler, 0, stopCh); err != nil {
return err
}
}
registerHandlers的路由内容如下:
// registerHandler registers handlers for yurtHubServer, and yurtHubServer can handle requests like profiling, healthz, update token.
func registerHandlers(c *mux.Router, cfg *config.YurtHubConfiguration, rest *rest.RestConfigManager) {
// register handlers for update join token
c.Handle("/v1/token", updateTokenHandler(cfg.CertManager)).Methods("POST", "PUT")
// register handler for health check
c.HandleFunc("/v1/healthz", healthz).Methods("GET")
c.Handle("/v1/readyz", readyz(cfg.CertManager)).Methods("GET")
// register handler for profile
if cfg.EnableProfiling {
profile.Install(c)
}
// register handler for metrics
c.Handle("/metrics", promhttp.Handler())
// register handler for ota upgrade
c.Handle("/pods", ota.GetPods(cfg.StorageWrapper)).Methods("GET")
c.Handle("/openyurt.io/v1/namespaces/{ns}/pods/{podname}/upgrade",
ota.HealthyCheck(rest, cfg.NodeName, ota.UpdatePod)).Methods("POST")
}
以上路由不做深入分析。
3.2. YurtHubProxyServerServing
YurtHubProxyServerServing主要代理kube-apiserver的转发请求。
// start yurthub proxy servers for forwarding requests to cloud kube-apiserver
if cfg.WorkingMode == util.WorkingModeEdge {
proxyHandler = wrapNonResourceHandler(proxyHandler, cfg, rest)
}
if cfg.YurtHubProxyServerServing != nil {
if err := cfg.YurtHubProxyServerServing.Serve(proxyHandler, 0, stopCh); err != nil {
return err
}
}
以下分析yurtProxyHandler的逻辑。
3.3. NewYurtReverseProxyHandler
NewYurtReverseProxyHandler主要创建了http handler 代理所有转发请求。
1、创建Load Balancer,主要用来转发apiserver的请求。
lb, err := remote.NewLoadBalancer(
yurtHubCfg.LBMode,
yurtHubCfg.RemoteServers,
localCacheMgr,
transportMgr,
coordinatorGetter,
cloudHealthChecker,
yurtHubCfg.FilterManager,
yurtHubCfg.WorkingMode,
stopCh)
2、创建local Proxy,主要用来转发本地缓存的请求。
// When yurthub works in Edge mode, we may use local proxy or pool proxy to handle
// the request when offline.
localProxy = local.NewLocalProxy(localCacheMgr,
cloudHealthChecker.IsHealthy,
isCoordinatorHealthy,
yurtHubCfg.MinRequestTimeout,
)
localProxy = local.WithFakeTokenInject(localProxy, yurtHubCfg.SerializerManager)
3、创建yurtReverseProxy
yurtProxy := &yurtReverseProxy{
resolver: resolver,
loadBalancer: lb,
cloudHealthChecker: cloudHealthChecker,
coordinatorHealtCheckerGetter: coordinatorHealthCheckerGetter,
localProxy: localProxy,
poolProxy: poolProxy,
maxRequestsInFlight: yurtHubCfg.MaxRequestInFlight,
isCoordinatorReady: isCoordinatorReady,
enablePoolCoordinator: yurtHubCfg.EnableCoordinator,
tenantMgr: tenantMgr,
workingMode: yurtHubCfg.WorkingMode,
}
return yurtProxy.buildHandlerChain(yurtProxy), nil
4. yurtReverseProxy
yurtReverseProxy主要是作为实现反向代理的结构体。
type yurtReverseProxy struct {
resolver apirequest.RequestInfoResolver
loadBalancer remote.LoadBalancer
cloudHealthChecker healthchecker.MultipleBackendsHealthChecker
coordinatorHealtCheckerGetter func() healthchecker.HealthChecker
localProxy http.Handler
poolProxy http.Handler
maxRequestsInFlight int
tenantMgr tenant.Interface
isCoordinatorReady func() bool
workingMode hubutil.WorkingMode
enablePoolCoordinator bool
}
反向代理服务
func (p *yurtReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if p.workingMode == hubutil.WorkingModeCloud {
p.loadBalancer.ServeHTTP(rw, req)
return
}
switch {
case util.IsKubeletLeaseReq(req):
p.handleKubeletLease(rw, req)
case util.IsEventCreateRequest(req):
p.eventHandler(rw, req)
case util.IsPoolScopedResouceListWatchRequest(req):
p.poolScopedResouceHandler(rw, req)
case util.IsSubjectAccessReviewCreateGetRequest(req):
p.subjectAccessReviewHandler(rw, req)
default:
// For resource request that do not need to be handled by pool-coordinator,
// handling the request with cloud apiserver or local cache.
if p.cloudHealthChecker.IsHealthy() {
p.loadBalancer.ServeHTTP(rw, req)
} else {
p.localProxy.ServeHTTP(rw, req)
}
}
}
核心逻辑:如果是云端apiserver可以访问的通,则通过loadbalaner来转发,否则就通过localproxy来转发读取本地节点的数据。
5. LoadBalancer
LoadBalancer是个本地的负载均衡逻辑,通过轮询的方式去请求cloud的apiserver,当云边网络通信是正常的时候反向代理apiserver的请求,并做本地缓存持久化。断网的时候则读取本地的缓存数据。
-
backends:真实反向代理的后端
-
algo: 处理负载均衡策略,轮询或者按优先级
-
localCacheMgr: 本地缓存管理的manager
type loadBalancer struct {
backends []*util.RemoteProxy
algo loadBalancerAlgo
localCacheMgr cachemanager.CacheManager
filterManager *manager.Manager
coordinatorGetter func() poolcoordinator.Coordinator
workingMode hubutil.WorkingMode
stopCh <-chan struct{}
}
5.1. NewLoadBalancer
NewLoadBalancer构建一个remote的反向代理,主要包含添加romote server proxy和处理负载均衡策略两部分。
1、添加多个apiserver的地址,创建remote proxy实现反向代理操作。
backends := make([]*util.RemoteProxy, 0, len(remoteServers))
for i := range remoteServers {
b, err := util.NewRemoteProxy(remoteServers[i], lb.modifyResponse, lb.errorHandler, transportMgr, stopCh)
if err != nil {
klog.Errorf("could not new proxy backend(%s), %v", remoteServers[i].String(), err)
continue
}
backends = append(backends, b)
}
2、处理负载均衡策略:
var algo loadBalancerAlgo
switch lbMode {
case "rr":
algo = &rrLoadBalancerAlgo{backends: backends, checker: healthChecker}
case "priority":
algo = &priorityLoadBalancerAlgo{backends: backends, checker: healthChecker}
default:
algo = &rrLoadBalancerAlgo{backends: backends, checker: healthChecker}
}
5.2. loadBalancer.ServeHTTP
loadBalancer实现ServeHTTP的接口,通过负载均衡策略挑选出一个可用的反向代理backend。再调用backend的ServeHTTP方法实现具体的反向代理操作。
// pick a remote proxy based on the load balancing algorithm.
rp := lb.algo.PickOne()
rp.ServeHTTP(rw, req)
5.3. errorHandler
如果请求apiserver失败,当verb=get/list, 则读取cache中的内容。
func (lb *loadBalancer) errorHandler(rw http.ResponseWriter, req *http.Request, err error) {
klog.Errorf("remote proxy error handler: %s, %v", hubutil.ReqString(req), err)
if lb.localCacheMgr == nil || !lb.localCacheMgr.CanCacheFor(req) {
rw.WriteHeader(http.StatusBadGateway)
return
}
ctx := req.Context()
if info, ok := apirequest.RequestInfoFrom(ctx); ok {
if info.Verb == "get" || info.Verb == "list" {
# 读取cache内容
if obj, err := lb.localCacheMgr.QueryCache(req); err == nil {
hubutil.WriteObject(http.StatusOK, obj, rw, req)
return
}
}
}
rw.WriteHeader(http.StatusBadGateway)
}
5.4. QueryCache
// QueryCache get runtime object from backend storage for request
func (cm *cacheManager) QueryCache(req *http.Request) (runtime.Object, error) {
ctx := req.Context()
info, ok := apirequest.RequestInfoFrom(ctx)
if !ok || info == nil || info.Resource == "" {
return nil, fmt.Errorf("failed to get request info for request %s", util.ReqString(req))
}
if !info.IsResourceRequest {
return nil, fmt.Errorf("failed to QueryCache for getting non-resource request %s", util.ReqString(req))
}
# 根据verb查询storage中的数据
switch info.Verb {
case "list":
return cm.queryListObject(req)
case "get", "patch", "update":
return cm.queryOneObject(req)
default:
return nil, fmt.Errorf("failed to QueryCache, unsupported verb %s of request %s", info.Verb, util.ReqString(req))
}
}
5.5. 查询storage中的数据
func (cm *cacheManager) queryOneObject(req *http.Request) (runtime.Object, error) {
...
klog.V(4).Infof("component: %s try to get key: %s", comp, key.Key())
obj, err := cm.storage.Get(key)
if err != nil {
klog.Errorf("failed to get obj %s from storage, %v", key.Key(), err)
return nil, err
}
...
}
目前存储有两种接口实现,一个是本地磁盘存储,一个是etcd存储。以下以磁盘存储为例分析。
// Get will get content from the regular file that specified by key.
// If key points to a dir, return ErrKeyHasNoContent.
func (ds *diskStorage) Get(key storage.Key) ([]byte, error) {
if err := utils.ValidateKey(key, storageKey{}); err != nil {
return []byte{}, storage.ErrKeyIsEmpty
}
storageKey := key.(storageKey)
if !ds.lockKey(storageKey) {
return nil, storage.ErrStorageAccessConflict
}
defer ds.unLockKey(storageKey)
path := filepath.Join(ds.baseDir, storageKey.Key())
buf, err := ds.fsOperator.Read(path)
switch err {
case nil:
return buf, nil
case fs.ErrNotExists:
return nil, storage.ErrStorageNotFound
case fs.ErrIsNotFile:
return nil, storage.ErrKeyHasNoContent
default:
return buf, fmt.Errorf("failed to read file at %s, %v", path, err)
}
}
6. RemoteProxy
RemoteProxy实现一个具体的反向代理操作。
字段说明:
-
reverseProxy:http的ReverseProxy
-
remoteServer:apiserver的地址
// RemoteProxy is an reverse proxy for remote server
type RemoteProxy struct {
reverseProxy *httputil.ReverseProxy
remoteServer *url.URL
currentTransport http.RoundTripper
bearerTransport http.RoundTripper
upgradeHandler *proxy.UpgradeAwareHandler
bearerUpgradeHandler *proxy.UpgradeAwareHandler
stopCh <-chan struct{}
}
实现ReverseProxy的ServeHTTP接口。
func (rp *RemoteProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if httpstream.IsUpgradeRequest(req) {
klog.V(5).Infof("get upgrade request %s", req.URL)
if isBearerRequest(req) {
rp.bearerUpgradeHandler.ServeHTTP(rw, req)
} else {
rp.upgradeHandler.ServeHTTP(rw, req)
}
return
}
rp.reverseProxy.ServeHTTP(rw, req)
}
实现错误处理的接口。
func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) {
klog.Errorf("failed while proxying request %s, %v", req.URL, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
7. LocalProxy
LocalProxy是一个当云边网络断开的时候,用于处理本地kubelet请求的数据的代理。
字段说明:
- cacheMgr:主要包含本地cache的一个处理管理器。
// LocalProxy is responsible for handling requests when remote servers are unhealthy
type LocalProxy struct {
cacheMgr manager.CacheManager
isCloudHealthy IsHealthy
isCoordinatorReady IsHealthy
minRequestTimeout time.Duration
}
LocalProxy实现ServeHTTP接口,根据不同的k8s请求类型,执行不同的操作:
-
watch:lp.localWatch(w, req)
-
create:lp.localPost(w, req)
-
delete, deletecollection: localDelete(w, req)
-
list., get, update:lp.localReqCache(w, req)
// ServeHTTP implements http.Handler for LocalProxy
func (lp *LocalProxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var err error
ctx := req.Context()
if reqInfo, ok := apirequest.RequestInfoFrom(ctx); ok && reqInfo != nil && reqInfo.IsResourceRequest {
klog.V(3).Infof("go into local proxy for request %s", hubutil.ReqString(req))
switch reqInfo.Verb {
case "watch":
err = lp.localWatch(w, req)
case "create":
err = lp.localPost(w, req)
case "delete", "deletecollection":
err = localDelete(w, req)
default: // list, get, update
err = lp.localReqCache(w, req)
}
if err != nil {
klog.Errorf("could not proxy local for %s, %v", hubutil.ReqString(req), err)
util.Err(err, w, req)
}
} else {
klog.Errorf("local proxy does not support request(%s), requestInfo: %s", hubutil.ReqString(req), hubutil.ReqInfoString(reqInfo))
util.Err(apierrors.NewBadRequest(fmt.Sprintf("local proxy does not support request(%s)", hubutil.ReqString(req))), w, req)
}
}
7.1. localReqCache
当边缘网络断连的时候,kubelet执行get list的操作时,通过localReqCache请求本地缓存的数据,返回给kubelet对应的k8s元数据。
// localReqCache handles Get/List/Update requests when remote servers are unhealthy
func (lp *LocalProxy) localReqCache(w http.ResponseWriter, req *http.Request) error {
if !lp.cacheMgr.CanCacheFor(req) {
klog.Errorf("can not cache for %s", hubutil.ReqString(req))
return apierrors.NewBadRequest(fmt.Sprintf("can not cache for %s", hubutil.ReqString(req)))
}
obj, err := lp.cacheMgr.QueryCache(req)
if errors.Is(err, storage.ErrStorageNotFound) || errors.Is(err, hubmeta.ErrGVRNotRecognized) {
klog.Errorf("object not found for %s", hubutil.ReqString(req))
reqInfo, _ := apirequest.RequestInfoFrom(req.Context())
return apierrors.NewNotFound(schema.GroupResource{Group: reqInfo.APIGroup, Resource: reqInfo.Resource}, reqInfo.Name)
} else if err != nil {
klog.Errorf("failed to query cache for %s, %v", hubutil.ReqString(req), err)
return apierrors.NewInternalError(err)
} else if obj == nil {
klog.Errorf("no cache object for %s", hubutil.ReqString(req))
return apierrors.NewInternalError(fmt.Errorf("no cache object for %s", hubutil.ReqString(req)))
}
return util.WriteObject(http.StatusOK, obj, w, req)
}
核心代码为:
查询本地缓存,返回缓存数据。
obj, err := lp.cacheMgr.QueryCache(req)
return util.WriteObject(http.StatusOK, obj, w, req)
总结
yurthub是实现边缘断网自治的核心组件,核心逻辑是kubelet向apiserver的请求会通过yurhub进行转发,如果apiserver的接口可通,则将请求结果返回,并存储到本地,如果接口不可通,则读取本地的数据。
yurthub本质是一个反向代理的http server, 核心逻辑主要包括 :
- proxy: 反向代理的实现
- cachemanager:cache的实现
- storage:本地存储的实现
参考:
14.2.3.2 - OpenYurt之TunnelServer源码分析
本文以commit id:
180282663457080119a1bc6076cce20c922b5c50, 对应版本tag:v1.2.1的源码分析tunnel-server的实现逻辑。
1. Tunnel-server简介
云与边一般位于不同网络平面,同时边缘节点普遍位于防火墙内部,采用云(中心)边协同架构,将导致原生 K8s 系统的运维监控能力面临如下挑战:
- K8s 原生运维能力缺失(如 kubectl logs/exec 等无法执行)
- 社区主流监控运维组件无法工作(如 Prometheus/metrics-server )
在 OpenYurt 中,引入了专门的组件 YurtTunnel 负责解决云边通信问题。反向通道是解决跨网络通信的一种常见方式,而 YurtTunnel 的本质也是一个反向通道。 它是一个典型的C/S结构的组件,由部署于云端的 YurtTunnelServer 和部署于边缘节点上的 YurtTunnelAgent组成。
本文主要分析tunnel-server的代理逻辑。
以下是基本架构图:
pkg中的目录结构:
yurttunnel
├── agent # tunnel agent代码
├── constants # 常量值
├── handlerwrapper
├── informers
├── kubernetes # k8s clientset工具包
├── server # 核心代码: tunnel server逻辑
├── trafficforward # iptables和dns操作
└── util
2. NewYurttunnelServerCommand
main函数入口:
func main() {
cmd := app.NewYurttunnelServerCommand(stop)
cmd.Flags().AddGoFlagSet(flag.CommandLine)
if err := cmd.Execute(); err != nil {
klog.Fatalf("%s failed: %s", projectinfo.GetServerName(), err)
}
}
以下是NewYurttunnelServerCommand构造函数,常见的三件套,不做展开:
-
读取参数:serverOptions.AddFlags(cmd.Flags())
-
生成配置:cfg.Complete()
-
执行Run函数:核心逻辑
func NewYurttunnelServerCommand(stopCh <-chan struct{}) *cobra.Command {
serverOptions := options.NewServerOptions()
cmd := &cobra.Command{
Use: "Launch " + projectinfo.GetServerName(),
Short: projectinfo.GetServerName() + " sends requests to " + projectinfo.GetAgentName(),
RunE: func(c *cobra.Command, args []string) error {
...
cfg, err := serverOptions.Config()
if err != nil {
return err
}
if err := Run(cfg.Complete(), stopCh); err != nil {
return err
}
return nil
},
Args: cobra.NoArgs,
}
serverOptions.AddFlags(cmd.Flags())
return cmd
}
3. Run(cfg.Complete(), stopCh)
Run函数最终是运行一个tunnelserver的常驻进程。在之前会做一些controller或manager的准备工作。
其中包括:
-
DNS controller
-
IP table manager
-
certificate manager
-
RegisterInformersForTunnelServer
首先是构建并运行上述的manager或controller, 源码中的注释也大概描述了主要流程:
-
注册tunnel所需的SharedInformerFactory
-
运行dns controller
-
运行ip table manager
-
给tunnel server创建certificate manager
-
给tunnel agent 创建certificate manager
-
创建handler wrappers
-
生成TLS 证书
-
运行tunnel server
以下是部分代码,已删除无关紧要的代码:
// run starts the yurttunel-server
func Run(cfg *config.CompletedConfig, stopCh <-chan struct{}) error {
var wg sync.WaitGroup
// register informers that tunnel server need
informers.RegisterInformersForTunnelServer(cfg.SharedInformerFactory)
// 0. start the DNS controller
if cfg.EnableDNSController {
dnsController, err := dns.NewCoreDNSRecordController(...)
go dnsController.Run(stopCh)
}
// 1. start the IP table manager
if cfg.EnableIptables {
iptablesMgr, err := iptables.NewIptablesManagerWithIPFamily(...)
wg.Add(1)
go iptablesMgr.Run(stopCh, &wg)
}
// 2. create a certificate manager for the tunnel server
certManagerFactory := certfactory.NewCertManagerFactory(cfg.Client)
ips, dnsNames, err := getTunnelServerIPsAndDNSNamesBeforeInformerSynced(cfg.Client, stopCh)
serverCertMgr, err := certManagerFactory.New(...)
serverCertMgr.Start()
// 3. create a certificate manager for the tunnel proxy client
tunnelProxyCertMgr, err := certManagerFactory.New(...)
tunnelProxyCertMgr.Start()
// 4. create handler wrappers
mInitializer := initializer.NewMiddlewareInitializer(cfg.SharedInformerFactory)
wrappers, err := wraphandler.InitHandlerWrappers(mInitializer, cfg.IsIPv6())
// after all of informers are configured completed, start the shared index informer
cfg.SharedInformerFactory.Start(stopCh)
// 5. waiting for the certificate is generated
_ = wait.PollUntil(5*time.Second, func() (bool, error) {
// keep polling until the certificate is signed
if serverCertMgr.Current() != nil && tunnelProxyCertMgr.Current() != nil {
return true, nil
}
klog.Infof("waiting for the master to sign the %s certificate", projectinfo.GetServerName())
return false, nil
}, stopCh)
// 6. generate the TLS configuration based on the latest certificate
tlsCfg, err := certmanager.GenTLSConfigUseCurrentCertAndCertPool(serverCertMgr.Current, cfg.RootCert, "server")
proxyClientTlsCfg, err := certmanager.GenTLSConfigUseCurrentCertAndCertPool(tunnelProxyCertMgr.Current, cfg.RootCert, "client")
// 7. start the server
ts := server.NewTunnelServer(
cfg.EgressSelectorEnabled,
cfg.InterceptorServerUDSFile,
cfg.ListenAddrForMaster,
cfg.ListenInsecureAddrForMaster,
cfg.ListenAddrForAgent,
cfg.ServerCount,
tlsCfg,
proxyClientTlsCfg,
wrappers,
cfg.ProxyStrategy)
if err := ts.Run(); err != nil {
return err
}
// 8. start meta server
util.RunMetaServer(cfg.ListenMetaAddr)
<-stopCh
wg.Wait()
return nil
}
3. TunnelServer
anpTunnelServer实现了TunnelServer的接口,以下分析TunnelServer.Run的部分。
run部分主要运行了三个server
-
proxyServer: 主要是重定向apiserver的请求到tunnel server
-
MasterServer:
-
AgentServer:主要运行一个grpc server与tunnel agent连接,即云边反向隧道
// Run runs the yurttunnel-server
func (ats *anpTunnelServer) Run() error {
proxyServer := anpserver.NewProxyServer(uuid.New().String(),
[]anpserver.ProxyStrategy{anpserver.ProxyStrategy(ats.proxyStrategy)},
ats.serverCount,
&anpserver.AgentTokenAuthenticationOptions{})
// 1. start the proxier
proxierErr := runProxier(
&anpserver.Tunnel{Server: proxyServer},
ats.egressSelectorEnabled,
ats.interceptorServerUDSFile,
ats.tlsCfg)
wrappedHandler, err := wh.WrapHandler(
NewRequestInterceptor(ats.interceptorServerUDSFile, ats.proxyClientTlsCfg),
ats.wrappers,
)
// 2. start the master server
masterServerErr := runMasterServer(
wrappedHandler,
ats.egressSelectorEnabled,
ats.serverMasterAddr,
ats.serverMasterInsecureAddr,
ats.tlsCfg)
// 3. start the agent server
agentServerErr := runAgentServer(ats.tlsCfg, ats.serverAgentAddr, proxyServer)
return nil
}
4. runAgentServer
runAgentServer主要运行一个grpc server与edge端的agent形成grpc连接。
// runAgentServer runs a grpc server that handles connections from the yurttunel-agent
// NOTE agent server is responsible for managing grpc connection yurttunel-server
// and yurttunnel-agent, and the proxy server is responsible for redirecting requests
// to corresponding yurttunel-agent
func runAgentServer(tlsCfg *tls.Config,
agentServerAddr string,
proxyServer *anpserver.ProxyServer) error {
serverOption := grpc.Creds(credentials.NewTLS(tlsCfg))
ka := keepalive.ServerParameters{
// Ping the client if it is idle for `Time` seconds to ensure the
// connection is still active
Time: constants.YurttunnelANPGrpcKeepAliveTimeSec * time.Second,
// Wait `Timeout` second for the ping ack before assuming the
// connection is dead
Timeout: constants.YurttunnelANPGrpcKeepAliveTimeoutSec * time.Second,
}
grpcServer := grpc.NewServer(serverOption,
grpc.KeepaliveParams(ka))
anpagent.RegisterAgentServiceServer(grpcServer, proxyServer)
listener, err := net.Listen("tcp", agentServerAddr)
klog.Info("start handling connection from agents")
if err != nil {
return fmt.Errorf("fail to listen to agent on %s: %w", agentServerAddr, err)
}
go grpcServer.Serve(listener)
return nil
}
5. Interceptor
Interceptor(请求拦截器)拦截kube-apiserver的请求转发给tunnel,通过tunnel请求kubelet。
流程图:
// NewRequestInterceptor creates a interceptor object that intercept request from kube-apiserver
func NewRequestInterceptor(udsSockFile string, cfg *tls.Config) *RequestInterceptor {
if len(udsSockFile) == 0 || cfg == nil {
return nil
}
cfg.InsecureSkipVerify = true
contextDialer := func(addr string, header http.Header, isTLS bool) (net.Conn, error) {
klog.V(4).Infof("Sending request to %q.", addr)
proxyConn, err := net.Dial("unix", udsSockFile)
if err != nil {
return nil, fmt.Errorf("dialing proxy %q failed: %w", udsSockFile, err)
}
var connectHeaders string
for _, h := range supportedHeaders {
if v := header.Get(h); len(v) != 0 {
connectHeaders = fmt.Sprintf("%s\r\n%s: %s", connectHeaders, h, v)
}
}
fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: localhost%s\r\n\r\n", addr, connectHeaders)
br := newBufioReader(proxyConn)
defer putBufioReader(br)
res, err := http.ReadResponse(br, nil)
if err != nil {
proxyConn.Close()
return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %w", addr, udsSockFile, err)
}
if res.StatusCode != 200 {
proxyConn.Close()
return nil, fmt.Errorf("proxy error from %s while dialing %s, code %d: %v", udsSockFile, addr, res.StatusCode, res.Status)
}
// if the request scheme is https, setup a tls connection over the
// proxy tunnel (i.e. interceptor <--tls--> kubelet)
if isTLS {
tlsTunnelConn := tls.Client(proxyConn, cfg)
if err := tlsTunnelConn.Handshake(); err != nil {
proxyConn.Close()
return nil, fmt.Errorf("fail to setup TLS handshake through the Tunnel: %w", err)
}
klog.V(4).Infof("successfully setup TLS connection to %q with headers: %s", addr, connectHeaders)
return tlsTunnelConn, nil
}
klog.V(2).Infof("successfully setup connection to %q with headers: %q", addr, connectHeaders)
return proxyConn, nil
}
return &RequestInterceptor{
contextDialer: contextDialer,
}
}
参考:
14.2.3.3 - OpenYurt之Tunnel-Agent源码分析
1. Tunnel-Agent简介
tunnel-agent是通过daemonset部署在每个worker节点,通过grpc协议与云端的tunnel-server建立连接。以下分析tunnel-agent的源码逻辑。
常用的启动参数:
- args:
- --node-name=$(NODE_NAME)
- --node-ip=$(POD_IP)
- --tunnelserver-addr=tunnel-server-address[ip:port]
- --v=2
command:
- yurt-tunnel-agent
2. NewYurttunnelAgentCommand
NewYurttunnelAgentCommand还是常用命令代码三板斧,此处不做展开,直接分析Run函数。
// NewYurttunnelAgentCommand creates a new yurttunnel-agent command
func NewYurttunnelAgentCommand(stopCh <-chan struct{}) *cobra.Command {
agentOptions := options.NewAgentOptions()
// 已经删除非重要的代码
cmd := &cobra.Command{
RunE: func(c *cobra.Command, args []string) error {
cfg, err := agentOptions.Config()
if err := Run(cfg.Complete(), stopCh); err != nil {
return err
}
},
}
agentOptions.AddFlags(cmd.Flags())
return cmd
}
3. Run
Run函数即启动一个agent服务,主要包含以下几个步骤:
-
先获取配置项tunnelserver-addr中的地址,如果地址不存在,则获取x-tunnel-server-svc的service 地址。(说明:一般情况下,tunnel-server跟agent不在同一个网络域,因此网络会不通,所以一般需要配置独立且可连通的地址,可以通过Nginx转发)
-
agent通过host的网络模式运行在宿主机上,启动证书manager。并等待证书生成。
-
生成连接tunnel-server的证书。
-
启动 yurttunnel-agent。
-
启动meta server。
// Run starts the yurttunel-agent
func Run(cfg *config.CompletedConfig, stopCh <-chan struct{}) error {
// 1. get the address of the yurttunnel-server
tunnelServerAddr = cfg.TunnelServerAddr
if tunnelServerAddr == "" {
if tunnelServerAddr, err = serveraddr.GetTunnelServerAddr(cfg.Client); err != nil {
return err
}
}
// 2. create a certificate manager
// As yurttunnel-agent will run on the edge node with Host network mode,
// we can use the status.podIP as the node IP
nodeIP := os.Getenv(constants.YurttunnelAgentPodIPEnv)
agentCertMgr, err = certfactory.NewCertManagerFactory(cfg.Client).New(&certfactory.CertManagerConfig{
ComponentName: projectinfo.GetAgentName(),
CertDir: cfg.CertDir,
SignerName: certificatesv1.KubeAPIServerClientSignerName,
CommonName: constants.YurtTunnelAgentCSRCN,
Organizations: []string{constants.YurtTunnelCSROrg},
DNSNames: []string{os.Getenv("NODE_NAME")},
IPs: []net.IP{net.ParseIP(nodeIP)},
})
agentCertMgr.Start()
// 2.1. waiting for the certificate is generated
_ = wait.PollUntil(5*time.Second, func() (bool, error) {
if agentCertMgr.Current() != nil {
return true, nil
}
klog.Infof("certificate %s not signed, waiting...",
projectinfo.GetAgentName())
return false, nil
}, stopCh)
// 3. generate a TLS configuration for securing the connection to server
tlsCfg, err := certmanager.GenTLSConfigUseCertMgrAndCA(agentCertMgr,
tunnelServerAddr, constants.YurttunnelCAFile)
// 4. start the yurttunnel-agent
ta := agent.NewTunnelAgent(tlsCfg, tunnelServerAddr, cfg.NodeName, cfg.AgentIdentifiers)
ta.Run(stopCh)
// 5. start meta server
util.RunMetaServer(cfg.AgentMetaAddr)
<-stopCh
return nil
}
4. TunnelAgent
TunnelAgent与tunnel-server建立tunnel,接收server的请求,并转发给kubelet。
// TunnelAgent sets up tunnel to TunnelServer, receive requests
// from tunnel, and forwards requests to kubelet
type TunnelAgent interface {
Run(<-chan struct{})
}
// NewTunnelAgent generates a new TunnelAgent
func NewTunnelAgent(tlsCfg *tls.Config,
tunnelServerAddr, nodeName, agentIdentifiers string) TunnelAgent {
ata := anpTunnelAgent{
tlsCfg: tlsCfg,
tunnelServerAddr: tunnelServerAddr,
nodeName: nodeName,
agentIdentifiers: agentIdentifiers,
}
return &ata
}
5. anpTunnelAgent.Run
anpTunnelAgent使用apiserver-network-proxy包来实现tunnel逻辑。项目具体参考:https://github.com/kubernetes-sigs/apiserver-network-proxy)
// RunAgent runs the yurttunnel-agent which will try to connect yurttunnel-server
func (ata *anpTunnelAgent) Run(stopChan <-chan struct{}) {
dialOption := grpc.WithTransportCredentials(credentials.NewTLS(ata.tlsCfg))
cc := &anpagent.ClientSetConfig{
Address: ata.tunnelServerAddr, // 指定反向连接的目标地址
AgentID: ata.nodeName,
AgentIdentifiers: ata.agentIdentifiers,
SyncInterval: 5 * time.Second,
ProbeInterval: 5 * time.Second,
DialOptions: []grpc.DialOption{dialOption},
ServiceAccountTokenPath: "",
}
// 调用apiserver-network-proxy的包来创建双向的grpc连接。
cs := cc.NewAgentClientSet(stopChan)
cs.Serve()
klog.Infof("start serving grpc request redirected from %s: %s",
projectinfo.GetServerName(), ata.tunnelServerAddr)
}
以下是apiserver-network-proxy的源码分析。
6. apiserver-network-proxy.Client分析
具体代码参考:
通过NewAgentClientSet创建一个client结构体。
func (cc *ClientSetConfig) NewAgentClientSet(stopCh <-chan struct{}) *ClientSet {
return &ClientSet{
clients: make(map[string]*Client),
agentID: cc.AgentID,
agentIdentifiers: cc.AgentIdentifiers,
address: cc.Address,
syncInterval: cc.SyncInterval,
probeInterval: cc.ProbeInterval,
dialOptions: cc.DialOptions,
serviceAccountTokenPath: cc.ServiceAccountTokenPath,
stopCh: stopCh,
}
}
6.1. client.Serve
client.Serve运行一个sync的goroutine的常驻进程,再调用syncOnce函数。
// 运行一个sync的goroutine
func (cs *ClientSet) Serve() {
go cs.sync()
}
// sync makes sure that #clients >= #proxy servers
func (cs *ClientSet) sync() {
defer cs.shutdown()
backoff := cs.resetBackoff()
var duration time.Duration
for {
if err := cs.syncOnce(); err != nil {
klog.ErrorS(err, "cannot sync once")
duration = backoff.Step()
} else {
backoff = cs.resetBackoff()
duration = wait.Jitter(backoff.Duration, backoff.Jitter)
}
time.Sleep(duration)
select {
case <-cs.stopCh:
return
default:
}
}
}
syncOnce运行了真正执行grpc通信的client。
func (cs *ClientSet) syncOnce() error {
if cs.serverCount != 0 && cs.ClientsCount() >= cs.serverCount {
return nil
}
// 创建封装的grpc client
c, serverCount, err := cs.newAgentClient()
if err != nil {
return err
}
if cs.serverCount != 0 && cs.serverCount != serverCount {
klog.V(2).InfoS("Server count change suggestion by server",
"current", cs.serverCount, "serverID", c.serverID, "actual", serverCount)
}
cs.serverCount = serverCount
if err := cs.AddClient(c.serverID, c); err != nil {
klog.ErrorS(err, "closing connection failure when adding a client")
c.Close()
return nil
}
klog.V(2).InfoS("sync added client connecting to proxy server", "serverID", c.serverID)
// 运行封装后的grpc 连接
go c.Serve()
return nil
}
7. Grpc client
代码参考:
7.1. newAgentClient
newAgentClient初始化一个grpc client,并启动连接。
func newAgentClient(address, agentID, agentIdentifiers string, cs *ClientSet, opts ...grpc.DialOption) (*Client, int, error) {
a := &Client{
cs: cs,
address: address,
agentID: agentID,
agentIdentifiers: agentIdentifiers,
opts: opts,
probeInterval: cs.probeInterval,
stopCh: make(chan struct{}),
serviceAccountTokenPath: cs.serviceAccountTokenPath,
connManager: newConnectionManager(),
}
// 启动client的连接
serverCount, err := a.Connect()
if err != nil {
return nil, 0, err
}
return a, serverCount, nil
}
7.2. connect
Connect使grpc连接代理服务器。它返回服务器ID
// Connect makes the grpc dial to the proxy server. It returns the serverID
// it connects to.
func (a *Client) Connect() (int, error) {
// 运行grpc dial连接
conn, err := grpc.Dial(a.address, a.opts...)
if err != nil {
return 0, err
}
// 已删除非必要的代码
// 创建stream
stream, err := agent.NewAgentServiceClient(conn).Connect(ctx)
if err != nil {
conn.Close() /* #nosec G104 */
return 0, err
}
serverID, err := serverID(stream)
if err != nil {
conn.Close() /* #nosec G104 */
return 0, err
}
serverCount, err := serverCount(stream)
if err != nil {
conn.Close() /* #nosec G104 */
return 0, err
}
a.conn = conn
a.stream = stream
a.serverID = serverID
klog.V(2).InfoS("Connect to", "server", serverID)
return serverCount, nil
}
7.3. Serve()
Serve主要启动grpc双向传输通道的goroutine, 主要包括 send(发)和recv(收)2个操作。
func (a *Client) Serve() {
// 已经删除次要代码
for {
// 收包
pkt, err := a.Recv()
klog.V(5).InfoS("[tracing] recv packet", "type", pkt.Type)
// 根据不同包类型进行不同的处理
switch pkt.Type {
case client.PacketType_DIAL_REQ:
resp := &client.Packet{
Type: client.PacketType_DIAL_RSP,
Payload: &client.Packet_DialResponse{DialResponse: &client.DialResponse{}},
}
if err := a.Send(resp); err != nil {
}
// 运行proxy
go a.remoteToProxy(connID, ctx)
go a.proxyToRemote(connID, ctx)
// 接收到数据
case client.PacketType_DATA:
data := pkt.GetData()
ctx, ok := a.connManager.Get(data.ConnectID)
if ok {
ctx.dataCh <- data.Data
}
case client.PacketType_CLOSE_REQ:
// 已删除
}
}
}
8. remoteToProxy和proxyToRemote
remoteToProxy
func (a *Client) remoteToProxy(connID int64, ctx *connContext) {
defer ctx.cleanup()
var buf [1 << 12]byte
resp := &client.Packet{
Type: client.PacketType_DATA,
}
for {
n, err := ctx.conn.Read(buf[:])
klog.V(4).InfoS("received data from remote", "bytes", n, "connID", connID)
// 删除次要代码
resp.Payload = &client.Packet_Data{Data: &client.Data{
Data: buf[:n],
ConnectID: connID,
}}
if err := a.Send(resp); err != nil {
klog.ErrorS(err, "stream send failure", "connID", connID)
}
}
}
}
proxyToRemote
func (a *Client) proxyToRemote(connID int64, ctx *connContext) {
defer ctx.cleanup()
for d := range ctx.dataCh {
pos := 0
for {
n, err := ctx.conn.Write(d[pos:])
if err == nil {
klog.V(4).InfoS("write to remote", "connID", connID, "lastData", n)
break
} else if n > 0 {
klog.ErrorS(err, "write to remote with failure", "connID", connID, "lastData", n)
pos += n
} else {
if !strings.Contains(err.Error(), "use of closed network connection") {
klog.ErrorS(err, "conn write failure", "connID", connID)
}
return
}
}
}
}
9. Recv() 和Send()
func (a *Client) Send(pkt *client.Packet) error {
a.sendLock.Lock()
defer a.sendLock.Unlock()
err := a.stream.Send(pkt)
if err != nil && err != io.EOF {
metrics.Metrics.ObserveFailure(metrics.DirectionToServer)
a.cs.RemoveClient(a.serverID)
}
return err
}
func (a *Client) Recv() (*client.Packet, error) {
a.recvLock.Lock()
defer a.recvLock.Unlock()
pkt, err := a.stream.Recv()
if err != nil && err != io.EOF {
metrics.Metrics.ObserveFailure(metrics.DirectionFromServer)
a.cs.RemoveClient(a.serverID)
}
return pkt, err
}
10. 总结
Tunnel-agent本质是封装了apiserver-network-proxy库,最终运行一个grpc的双向收发数据包的通道,所以**本质上tunnel是通过grpc反向建立连接,并实现双向通信的能力。**因此该反向隧道能力的也可以通过其他双向通信的协议来实现,例如websocket(类似kubeedge通过websocket来实现反向隧道)。
参考:
15 - 虚拟化
15.1 - 虚拟化相关概念
1. 虚拟化
借助虚拟化技术,用户能以单个物理硬件系统为基础,创建多个模拟环境或专用资源,并使用一款名为“Hypervisor”(虚拟机监控程序)的软件直接连接到硬件,从而将一个系统划分为不同、单独而安全的环境,即虚拟机 (VM)。
虚拟化技术可以重新划分IT资源,提高资源的利用率。
2. 虚拟化的类型
全虚拟化(Full virtualization)
全虚拟化使用未修改的guest操作系统版本,guest直接与CPU通信,是最快的虚拟化方法。
半虚拟化(Paravirtualization)
半虚拟化使用修改过的guest操作系统,guest与hypervisor通信,hypervisor将guest的调用传递给CPU和其他接口。因为通信经过hypervisor,因此比全虚拟化慢。
3. hypervisor
hypervisor又称为 virtual machine monitor (VMM),是一个创建和运行虚拟机的程序。被 hypervisor 用来运行一个或多个虚拟机的计算机称为宿主机(host machine),这些虚拟机则称为客户机(guest machine)。
在虚拟化技术中,Hypervisor(虚拟机监视器)是一种软件、固件或硬件,能够创建和运行虚拟机(VM)。Hypervisor 起到物理硬件与虚拟机之间的中介作用,管理和分配物理资源(如 CPU、内存、存储和网络)给虚拟机,并确保每个虚拟机的隔离性和安全性。
Hypervisor 的功能
- 虚拟化资源管理:
- 分配物理硬件资源(CPU、内存、存储、网络)给虚拟机。
- 管理和调度虚拟机的运行。
- 虚拟机隔离:
- 确保虚拟机之间的隔离性,防止一个虚拟机的故障或安全问题影响其他虚拟机。
- 硬件抽象:
- 抽象底层硬件,使虚拟机可以运行在不同的硬件平台上,而无需修改虚拟机内的操作系统和应用程序。
- 资源优化:
- 动态调整虚拟机资源,提供负载均衡、资源共享和节能功能。
- 高可用性和容错:
- 提供虚拟机快照、备份和恢复功能,确保高可用性和数据安全。
- 支持虚拟机迁移和克隆,方便运维管理。
Hypervisor 的优点
- 硬件利用率:通过虚拟化,可以更高效地利用物理硬件资源,减少硬件浪费。
- 灵活性:虚拟机可以轻松创建、删除和迁移,方便开发、测试和部署。
- 隔离性:虚拟机之间相互隔离,提高系统安全性和稳定性。
- 成本效益:减少对物理硬件的需求,降低硬件和维护成本。
4. kvm
kvm(Kernel-based Virtual Machine)是Linux内核的虚拟化模块,可以利用Linux内核的功能来作为hypervisor。
KVM本身不进行模拟,而是暴露一个/dev/kvm接口。
使用KVM,可以在Linux的镜像上
5. qemu
QEMU(Quick Emulator)是一个开源的硬件虚拟化和仿真器软件。它被广泛用于创建和运行虚拟机,支持多种不同的硬件平台和操作系统。以下是对 QEMU 的详细介绍:
QEMU 的主要特点
- 虚拟化和仿真:
- 虚拟化:QEMU 能够利用硬件虚拟化技术(如 Intel VT-x 和 AMD-V)来运行虚拟机。通过硬件辅助虚拟化,QEMU 可以提供接近原生性能的虚拟机运行环境。
- 仿真:QEMU 还可以完全在软件中仿真硬件,无需硬件虚拟化支持。这种模式下,QEMU 能够仿真多种不同的 CPU 架构(如 x86、ARM、MIPS、PowerPC 等),适用于跨平台开发和测试。
- 多种平台支持:
- QEMU 支持多种不同的主机平台和目标平台,能够在一个平台上运行不同架构的操作系统。这使得 QEMU 成为跨平台开发和测试的理想工具。
- 与 KVM 集成:
- QEMU 可以与内核虚拟机(KVM)集成使用,以提高虚拟化性能。KVM 提供了基于 Linux 内核的高效虚拟化解决方案,而 QEMU 提供了强大的虚拟机管理和仿真功能。
- 设备仿真:
- QEMU 提供了丰富的设备仿真功能,包括网络接口、磁盘存储、图形显示、USB 设备等。这些设备仿真使得虚拟机能够模拟真实硬件环境,方便开发和测试。
- 快照和恢复:
- QEMU 支持虚拟机快照功能,允许用户在特定时刻保存虚拟机的状态,并在需要时恢复到该状态。这对于调试和测试非常有用。
6. libvirt
Libvirt 是一个开源的虚拟化管理框架和工具集,用于管理不同的虚拟化技术,包括 KVM、QEMU、Xen、VMware ESXi、Hyper-V 以及其他一些虚拟化平台。它提供了一个统一的 API,用于创建、管理和监控虚拟机,使得对不同虚拟化技术的操作更加简化和一致。
6.1. 特点
- 统一接口
- 提供了一个统一的 API 和命令行工具,使用户和开发者可以通过一致的接口管理不同的虚拟化技术。
- 多种虚拟化技术支持:
- 支持多种虚拟化平台,如 KVM、QEMU、Xen、VMware ESXi、Hyper-V、LXC(Linux 容器)等,提供跨平台的虚拟化管理功能。
- 管理和监控:
- 提供强大的管理和监控功能,包括创建、启动、停止、迁移虚拟机,以及管理存储和网络资源。
- XML 配置:
- 使用 XML 配置文件来定义虚拟机和资源,提供灵活和可扩展的配置方式,易于保存和版本控制。
- 安全性:
- 支持多种安全机制,如基于 SELinux 的安全策略,确保虚拟机和宿主系统的隔离性和安全性。
- 网络管理:
- 提供网络桥接、NAT、虚拟交换机等网络管理功能,支持复杂的网络拓扑配置。
- 存储管理:
- 支持多种存储后端,如本地磁盘、网络文件系统(NFS)、iSCSI、LVM 等,方便虚拟机的存储资源管理。
6.2. 主要组件
- libvirtd:
- Libvirt 的守护进程,负责处理客户端请求和管理虚拟机。通过这个守护进程,可以与不同的虚拟化后端进行交互。
- virsh:
- Libvirt 的命令行工具,提供了一系列命令用于管理虚拟机和资源。通过
virsh,用户可以执行创建、启动、停止、迁移虚拟机等操作。
- Libvirt 的命令行工具,提供了一系列命令用于管理虚拟机和资源。通过
- libvirt API:
- 提供 C 语言的库和 API,同时支持多种编程语言的绑定,如 Python、Perl、Ruby、Java、Go 等,使得开发者可以在不同的编程环境中使用 Libvirt 的功能。
6.3. virsh命令
# 列出所有虚拟机
virsh list --all
# 启动虚拟机
virsh start <vm_name>
# 关闭虚拟机
virsh shutdown <vm_name>
# 定义新的虚拟机
virsh define /path/to/vm.xml
# 创建并启动虚拟机
virsh create /path/to/vm.xml
# 迁移虚拟机
virsh migrate --live <vm_name> qemu+ssh://destination_host/system
参考:
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/virtualization_getting_started_guide/chap-virtualization_getting_started-what_is_it
- https://en.wikipedia.org/wiki/Hypervisor
- https://www.linux-kvm.org/page/Main_Page
- http://www.linux-kvm.org/page/Documents
- https://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine
- https://wiki.qemu.org/Index.html
15.2 - KubeVirt
15.2.1 - KubeVirt的介绍
本文主要由云原生虚拟化:基于 Kubevirt 构建边缘计算实例文章重新整理而成。
1. kubevirt简介
kubevirt是基于k8s之上,提供了一种通过k8s来编排和管理虚拟机的方式。
2. 架构图
2.1. 组件说明
| 分类 | 组件 | 部署方式 | 功能说明 |
|---|---|---|---|
| 控制面 | virt-api | deployment | 自定义API,开机、关机、重启等,作为apiserver的插件,业务通过k8s apiserver请求virt-api。 |
| virt-controller | deployment | 管理和监控VMI对象的状态,控制VMI下的pod。 | |
| 节点侧 | virt-handler | daemonset | 类似kubelet,管理宿主机上的所有虚拟机实例。 |
| virt-launcher | virt-handler pod | 调用libvirt和qemu创建虚拟机进程。 |
virt-launcher与libvirt逻辑:
2.2. 自定义CRD对象
| 分类 | CRD对象 | 功能说明 |
|---|---|---|
| 虚机 | VirtualMachineInstance(VMI) | 代表运行的虚拟机实例 |
| VirtualMachine(VM) | 虚机对象,提供开机、关机、重启,管理VMI实例,与VMI的关系是1:1 | |
3. 创建虚拟机流程
待补充
参考:
15.2.2 - KubeVirt的使用
1. 安装kubevirt
1.1. 修改镜像仓库
针对私有环境,需要将所需镜像上传到自己的镜像仓库中。
涉及的镜像组件有
virt-operator
virt-api
virt-controller
virt-launcher
重命名镜像脚本如下:
#!/bin/bash
# kubevirt组件版本
version=$1
# 私有镜像仓库
registry=$2
# 私有镜像仓库的namespace
namespace=$3
kubevirtRegistry="quay.io/kubevirt"
readonly APPLIST=(
virt-operator
virt-api
virt-controller
virt-launcher
)
for app in "${APPLIST[@]}"; do
# 拉取镜像
docker pull ${kubevirtRegistry}/${app}:${version}
# 重命名
docker tag ${kubevirtRegistry}/${app}:${version} ${registry}/${namespace}/${app}:${version}
# 推送镜像
docker push ${registry}/${namespace}/${app}:${version}
done
echo "重新命名成功"
1.2. 部署virt-operator
通过kubevirt operator安装kubevirt相关组件,选择指定版本,下载kubevirt-operator.yaml和kubevirt-cr.yaml文件,并创建k8s相关对象。
如果是私有镜像仓库,则需要将kubevirt-operator.yaml文件中镜像的名字替换为私有镜像仓库的地址,并提前按步骤1推送所需镜像到私有镜像仓库。
# Pick an upstream version of KubeVirt to install
$ export RELEASE=v0.52.0
# Deploy the KubeVirt operator
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/kubevirt-operator.yaml
# Create the KubeVirt CR (instance deployment request) which triggers the actual installation
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/kubevirt-cr.yaml
# wait until all KubeVirt components are up
$ kubectl -n kubevirt wait kv kubevirt --for condition=Available
1.3. 部署virtctl
virtctl用来启动和关闭虚拟机。
VERSION=$(kubectl get kubevirt.kubevirt.io/kubevirt -n kubevirt -o=jsonpath="{.status.observedKubeVirtVersion}")
ARCH=$(uname -s | tr A-Z a-z)-$(uname -m | sed 's/x86_64/amd64/') || windows-amd64.exe
echo ${ARCH}
curl -L -o virtctl https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/virtctl-${VERSION}-${ARCH}
chmod +x virtctl
sudo install virtctl /usr/local/bin
2. kubevirt部署产物
通过手动部署virt-operator,会自动部署以下组件
| 组件 | 部署方式 | 副本数 |
|---|---|---|
| virt-api | deployment | 2 |
| virt-controller | deployment | 2 |
| virt-handler | daemonset | - |
具体参考:
#kg all -n kubevirt
NAME READY STATUS RESTARTS AGE
pod/virt-api-5fb5cffb7f-hgjjh 1/1 Running 0 23h
pod/virt-api-5fb5cffb7f-jcp7x 1/1 Running 0 23h
pod/virt-controller-844cd4f58c-h8vsx 1/1 Running 0 23h
pod/virt-controller-844cd4f58c-vlxqs 1/1 Running 0 23h
pod/virt-handler-lb5ft 1/1 Running 0 23h
pod/virt-handler-mtr4d 1/1 Running 0 22h
pod/virt-handler-sxd2t 1/1 Running 0 23h
pod/virt-operator-8595f577cd-b9txg 1/1 Running 0 23h
pod/virt-operator-8595f577cd-p2f69 1/1 Running 0 23h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubevirt-operator-webhook ClusterIP 10.254.159.81 <none> 443/TCP 23h
service/kubevirt-prometheus-metrics ClusterIP 10.254.7.231 <none> 443/TCP 23h
service/virt-api ClusterIP 10.254.244.139 <none> 443/TCP 23h
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/virt-handler 3 3 3 3 3 kubernetes.io/os=linux 23h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/virt-api 2/2 2 2 23h
deployment.apps/virt-controller 2/2 2 2 23h
deployment.apps/virt-operator 2/2 2 2 23h
NAME DESIRED CURRENT READY AGE
replicaset.apps/virt-api-5fb5cffb7f 2 2 2 23h
replicaset.apps/virt-controller-844cd4f58c 2 2 2 23h
replicaset.apps/virt-operator-8595f577cd 2 2 2 23h
NAME AGE PHASE
kubevirt.kubevirt.io/kubevirt 23h Deployed
3. 创建虚拟机
通过vm.yaml创建虚拟机
# 下载vm.yaml
wget https://kubevirt.io/labs/manifests/vm.yaml
# 创建虚拟机
kubectl apply -f https://kubevirt.io/labs/manifests/vm.yaml
vm.yaml文件
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: testvm
spec:
running: false
template:
metadata:
labels:
kubevirt.io/size: small
kubevirt.io/domain: testvm
spec:
domain:
devices:
disks:
- name: containerdisk
disk:
bus: virtio
- name: cloudinitdisk
disk:
bus: virtio
interfaces:
- name: default
masquerade: {}
resources:
requests:
memory: 64M
networks:
- name: default
pod: {}
volumes:
- name: containerdisk
containerDisk:
image: quay.io/kubevirt/cirros-container-disk-demo
- name: cloudinitdisk
cloudInitNoCloud:
userDataBase64: SGkuXG4=
查看虚拟机
kubectl get vms
kubectl get vms -o yaml testvm
启动或暂停虚拟机
# 启动虚拟机
virtctl start testvm
# 关闭虚拟机
virtctl stop testvm
# 进入虚拟机
virtctl console testvm
删除虚拟机
kubectl delete vm testvm
参考:
15.3 - qemu创建虚拟机
1. 部署qemu-system-x86_64
# 更新包
sudo apt-get update
# 安装QEMU和KVM相关的包。KVM(Kernel-based Virtual Machine)可以显著提高QEMU的性能。
sudo apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
# 安装qemu-system-x86
sudo apt-get install -y qemu qemu-system-x86
# 为了在非root用户下使用QEMU/KVM,需要将当前用户添加到 libvirt 和 kvm 组。
sudo usermod -aG libvirt $(whoami)
sudo usermod -aG kvm $(whoami)
# 查看版本
qemu-system-x86_64 --version
# 验证命令
virsh list --all
16 - 监控体系
16.1 - kube-prometheus-stack的使用
1. kube-prometheus-stack简介
kube-prometheus-stack是prometheus监控k8s集群的套件,可以通过helm一键安装,同时带有监控的模板。
各组件包括
- grafana
- kube-state-metrics
- prometheus
- alertmanager
- node-exporter
2. 安装kube-prometheus-stack
执行以下命令
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n prometheus
示例:
# helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n prometheus
NAME: kube-prometheus-stack
LAST DEPLOYED: Wed May 17 17:12:24 2023
NAMESPACE: prometheus
STATUS: deployed
REVISION: 1
NOTES:
kube-prometheus-stack has been installed. Check its status by running:
kubectl --namespace prometheus get pods -l "release=kube-prometheus-stack"
Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create & configure Alertmanager and Prometheus instances using the Operator.
3. 查看安装结果
-
deployment
- kube-prometheus-stack-grafana
- kube-prometheus-stack-kube-state-metrics
- kube-prometheus-stack-operator
-
statefulset
- prometheus-kube-prometheus-stack-prometheus
- alertmanager-kube-prometheus-stack-alertmanager
-
daemonset
- kube-prometheus-stack-prometheus-node-exporter
# kg all -n prometheus
NAME READY STATUS RESTARTS AGE
pod/alertmanager-kube-prometheus-stack-alertmanager-0 2/2 Running 0 9m34s
pod/kube-prometheus-stack-grafana-5bb7689dc8-lgrws 3/3 Running 0 9m35s
pod/kube-prometheus-stack-kube-state-metrics-5d6578867c-25xbq 1/1 Running 0 9m35s
pod/kube-prometheus-stack-operator-9c5fbdc68-nrn7h 1/1 Running 0 9m35s
pod/kube-prometheus-stack-prometheus-node-exporter-8ghd8 1/1 Running 0 48s
pod/kube-prometheus-stack-prometheus-node-exporter-brtp9 1/1 Running 0 29s
pod/kube-prometheus-stack-prometheus-node-exporter-n4kdp 1/1 Running 0 88s
pod/kube-prometheus-stack-prometheus-node-exporter-ttksv 1/1 Running 0 35s
pod/prometheus-kube-prometheus-stack-prometheus-0 2/2 Running 0 9m34s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 9m34s
service/kube-prometheus-stack-alertmanager ClusterIP 10.99.108.180 <none> 9093/TCP 9m36s
service/kube-prometheus-stack-grafana ClusterIP 10.110.62.28 <none> 80/TCP 9m36s
service/kube-prometheus-stack-kube-state-metrics ClusterIP 10.110.105.139 <none> 8080/TCP 9m35s
service/kube-prometheus-stack-operator ClusterIP 10.96.147.204 <none> 443/TCP 9m36s
service/kube-prometheus-stack-prometheus ClusterIP 10.98.235.203 <none> 9090/TCP 9m36s
service/kube-prometheus-stack-prometheus-node-exporter ClusterIP 10.105.99.77 <none> 9100/TCP 9m36s
service/prometheus-operated ClusterIP None <none> 9090/TCP 9m34s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/kube-prometheus-stack-prometheus-node-exporter 4 4 4 4 4 <none> 9m35s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kube-prometheus-stack-grafana 1/1 1 1 9m35s
deployment.apps/kube-prometheus-stack-kube-state-metrics 1/1 1 1 9m35s
deployment.apps/kube-prometheus-stack-operator 1/1 1 1 9m35s
NAME READY AGE
statefulset.apps/alertmanager-kube-prometheus-stack-alertmanager 1/1 9m34s
statefulset.apps/prometheus-kube-prometheus-stack-prometheus 1/1 9m34s
4. 登录grafana
默认账号密码
账号:admin
密码:prom-operator
默认账号密码位于secret中,通过base64解码可得上述密码。
kg secret -n prometheus kube-prometheus-stack-grafana -oyaml
apiVersion: v1
data:
admin-password: cHJvbS1vcGVyYXRvcg==
admin-user: YWRtaW4=
模板内容:

pod数据:

参考:
16.2 - Kubernetes集群监控
1. 概述
1.1. cAdvisor
cAdvisor对Node机器上的资源及容器进行实时监控和性能数据采集,包括CPU使用情况、内存使用情况、网络吞吐量及文件系统使用情况,cAdvisor集成在Kubelet中,当kubelet启动时会自动启动cAdvisor,即一个cAdvisor仅对一台Node机器进行监控。kubelet的启动参数--cadvisor-port可以定义cAdvisor对外提供服务的端口,默认为4194。可以通过浏览器Node_IP:port访问。项目主页:http://github.com/google/cadvisor。
1.2. Heapster
是对集群中的各个Node、Pod的资源使用数据进行采集,通过访问每个Node上Kubelet的API,再通过Kubelet调用cAdvisor的API来采集该节点上所有容器的性能数据。由Heapster进行数据汇聚,保存到后端存储系统中,例如InfluxDB,Google Cloud Logging等。项目主页为:https://github.com/kubernetes/heapster。
1.3. InfluxDB
是分布式时序数据库(每条记录带有时间戳属性),主要用于实时数据采集、事件跟踪记录、存储时间图表、原始数据等。提供REST API用于数据的存储和查询。项目主页为http://InfluxDB.com。
1.4. Grafana
通过Dashboard将InfluxDB的时序数据展现成图表形式,便于查看集群运行状态。项目主页为http://Grafana.org。
1.5. 总体架构图

其中当前Kubernetes中,Heapster、InfluxDB、Grafana均以Pod的形式启动和运行。Heapster与Master需配置安全连接。
2. 部署与使用
2.1. cAdvisor
kubelet的启动参数--cadvisor-port可以定义cAdvisor对外提供服务的端口,默认为4194。可以通过浏览器Node_IP:port访问。也提供了REST API供客户端远程调用,API返回的格式为JSON,可以采用URL访问:http://hostname:port/api/version/request/
例如:http://14.152.49.100:4194/api/v1.3/machine 获取主机信息。
2.2. Service
2.2.1. heapster-service
heapster-service.yaml
apiVersion:v1
kind:Service
metadata:
label:
kubenetes.io/cluster-service:"true"
kubernetes.io/name:Heapster
name:heapster
namespace:kube-system
spec:
ports:
- port:80
targetPort:8082
selector:
k8s-app:heapster
2.2.2. influxdb-service
influxdb-service.yaml
apiVersion:v1
kind:Service
metadata:
label:null
name:monitoring-InfluxDB
namespace:kube-system
spec:
type:Nodeport
ports:
- name:http
port:80
targetPort:8083
- name:api
port:8086
targetPort:8086
Nodeport:8086
selector:
name:influxGrafana
2.2.3. grafana-service
grafana-service.yaml
apiVersion:v1
kind:Service
metadata:
label:
kubenetes.io/cluster-service:"true"
kubernetes.io/name:monitoring-Grafana
name:monitoring-Grafana
namespace:kube-system
spec:
type:Nodeport
ports:
port:80
targetPort:8080
Nodeport:8085
selector:
name:influxGrafana
使用type=NodePort将InfluxDB和Grafana暴露在Node的端口上,以便通过浏览器进行访问。
2.2.4. 创建service
kubectl create -f heapster-service.yaml
kubectl create -f InfluxDB-service.yaml
kubectl create -f Grafana-service.yaml
2.3. ReplicationController
2.3.1. influxdb-grafana-controller
influxdb-grafana-controller-v3.yaml
apiVersion:v1
kind:ReplicationController
metadata:
name:monitoring-influxdb-grafana-v3
namespace:kube-system
labels:
k8s-app:influxGrafana
version:v3
kubernetes.io/cluster-service:"true
spec:
replicas:1
selector:
k8s-app:influxGrafana
version:v3
template:
metadata:
labels:
k8s-app:influxGrafana
version:v3
kubernetes.io/cluster-service:"true
spec:
containers:
- image:gcr.io/google_containers/heapster_influxdb:v0.5
name:influxdb
resources:
limits:
cpu:100m
memory:500Mi
requests:
cpu:100m
memory:500Mi
ports:
- containerPort:8083
- containerPort:8086
volumeMounts:
-name:influxdb-persistent-storage
mountPath:/data
- image:grc.io/google_containers/heapster_grafana:v2.6.0-2
name:grafana
resources:
limits:
cpu:100m
memory:100Mi
requests:
cpu:100m
memory:100Mi
env:
- name:INFLUXDB_SERVICE_URL
value:http://monitoring-influxdb:8086
- name:GF_AUTH_BASIC_ENABLED
value:"false"
- name:GF_AUTH_ANONYMOUS_ENABLED
value:"true"
- name:GF_AUTH_ANONYMOUS_ORG_ROLE
value:Admin
- name:GF_SERVER_ROOT_URL
value:/api/v1/proxy/namespace/kube-system/services/monitoring-grafana/
volumeMounts:
- name:grafana-persistent-storage
mountPath:/var
volumes:
- name:influxdb-persistent-storage
emptyDir{}
- name:grafana-persistent-storage
emptyDir{}
2.3.2. heapster-controller
heapster-controller.yaml
apiVersion:v1
kind:ReplicationController
metadata:
labels:
k8s-app:heapster
name:heapster
version:v6
name:heapster
namespace:kube-system
spec:
replicas:1
selector:
name:heapster
k8s-app:heapster
version:v6
template:
metadata:
labels:
k8s-app:heapster
version:v6
spec:
containers:
- image:gcr.io/google_containers/heapster:v0.17.0
name:heapster
command:
- /heapster
- --source=kubernetes:http://192.168.1.128:8080?inClusterConfig=flase&kubeletHttps=true&useServiceAccount=true&auth=
- --sink=InfluxDB:http://monitoring-InfluxDB:8086
Heapster设置启动参数说明:
1、–source
配置监控来源,本例中表示从k8s-Master获取各个Node的信息。在URL的参数部分,修改kubeletHttps、inClusterConfig、useServiceAccount的值。
2、–sink
配置后端的存储系统,本例中使用InfluxDB。URL中主机名的地址是InfluxDB的Service名字,需要DNS服务正常工作,如果没有配置DNS服务可使用Service的ClusterIP地址。
2.3.3. 创建ReplicationController
kubelet create -f InfluxDB-Grafana-controller.yaml
kubelet create -f heapster-controller.yaml
3. 查看界面及数据
3.1. InfluxDB
访问任意一台Node机器的30083端口。
3.2. Grafana
访问任意一台Node机器的30080端口。
4. 容器化部署
4.1. 拉取镜像
docker pull influxdb:latest
docker pull cadvisor:latest
docker pull grafana:latest
docker pull heapster:latest
4.2. 运行容器
4.2.1. influxdb
#influxdb
docker run -d -p 8083:8083 -p 8086:8086 --expose 8090 --expose 8099 --volume=/opt/data/influxdb:/data --name influxsrv influxdb:latest
4.2.2. cadvisor
#cadvisor
docker run --volume=/:/rootfs:ro --volume=/var/run:/var/run:rw --volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:ro --publish=8080:8080 --detach=true --link influxsrv:influxsrv --name=cadvisor cadvisor:latest -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086
4.2.3. grafana
#grafana
docker run -d -p 3000:3000 -e INFLUXDB_HOST=influxsrv -e INFLUXDB_PORT=8086 -e INFLUXDB_NAME=cadvisor -e INFLUXDB_USER=root -e INFLUXDB_PASS=root --link influxsrv:influxsrv --name grafana grafana:latest
4.2.4. heapster
docker run -d -p 8082:8082 --net=host heapster:canary --source=kubernetes:http://`k8s-server-ip`:8080?inClusterConfig=false/&useServiceAccount=false --sink=influxdb:http://`influxdb-ip`:8086
4.3. 访问
在浏览器输入IP:PORT
16.3 - cAdvisor介绍
1. cAdvisor简介
cAdvisor对Node机器上的资源及容器进行实时监控和性能数据采集,包括CPU使用情况、内存使用情况、网络吞吐量及文件系统使用情况,cAdvisor集成在Kubelet中,当kubelet启动时会自动启动cAdvisor,即一个cAdvisor仅对一台Node机器进行监控。kubelet的启动参数--cadvisor-port可以定义cAdvisor对外提供服务的端口,默认为4194。可以通过浏览器<Node_IP:port>访问。项目主页:http://github.com/google/cadvisor。
2. cAdvisor结构图

3. Metrics
| 分类 | 字段 | 描述 |
|---|---|---|
| cpu | cpu_usage_total | |
| cpu_usage_system | ||
| cpu_usage_user | ||
| cpu_usage_per_cpu | ||
| load_average | Smoothed average of number of runnable threads x 1000 | |
| memory | memory_usage | Memory Usage |
| memory_working_set | Working set size | |
| network | rx_bytes | Cumulative count of bytes received |
| rx_errors | Cumulative count of receive errors encountered | |
| tx_bytes | Cumulative count of bytes transmitted | |
| tx_errors | Cumulative count of transmit errors encountered | |
| filesystem | fs_device | Filesystem device |
| fs_limit | Filesystem limit | |
| fs_usage | Filesystem usage |
4. cAdvisor源码
4.1. cAdvisor入口函数
cadvisor.go
func main() {
defer glog.Flush()
flag.Parse()
if *versionFlag {
fmt.Printf("cAdvisor version %s (%s)/n", version.Info["version"], version.Info["revision"])
os.Exit(0)
}
setMaxProcs()
memoryStorage, err := NewMemoryStorage()
if err != nil {
glog.Fatalf("Failed to initialize storage driver: %s", err)
}
sysFs, err := sysfs.NewRealSysFs()
if err != nil {
glog.Fatalf("Failed to create a system interface: %s", err)
}
collectorHttpClient := createCollectorHttpClient(*collectorCert, *collectorKey)
containerManager, err := manager.New(memoryStorage, sysFs, *maxHousekeepingInterval, *allowDynamicHousekeeping, ignoreMetrics.MetricSet, &collectorHttpClient)
if err != nil {
glog.Fatalf("Failed to create a Container Manager: %s", err)
}
mux := http.NewServeMux()
if *enableProfiling {
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
}
// Register all HTTP handlers.
err = cadvisorhttp.RegisterHandlers(mux, containerManager, *httpAuthFile, *httpAuthRealm, *httpDigestFile, *httpDigestRealm)
if err != nil {
glog.Fatalf("Failed to register HTTP handlers: %v", err)
}
cadvisorhttp.RegisterPrometheusHandler(mux, containerManager, *prometheusEndpoint, nil)
// Start the manager.
if err := containerManager.Start(); err != nil {
glog.Fatalf("Failed to start container manager: %v", err)
}
// Install signal handler.
installSignalHandler(containerManager)
glog.Infof("Starting cAdvisor version: %s-%s on port %d", version.Info["version"], version.Info["revision"], *argPort)
addr := fmt.Sprintf("%s:%d", *argIp, *argPort)
glog.Fatal(http.ListenAndServe(addr, mux))
}
核心代码:
memoryStorage, err := NewMemoryStorage()
sysFs, err := sysfs.NewRealSysFs()
#创建containerManager
containerManager, err := manager.New(memoryStorage, sysFs, *maxHousekeepingInterval, *allowDynamicHousekeeping, ignoreMetrics.MetricSet, &collectorHttpClient)
#启动containerManager
err := containerManager.Start()
4.2. cAdvisor Client的使用
import "github.com/google/cadvisor/client"
func main(){
client, err := client.NewClient("http://192.168.19.30:4194/") //http://<host-ip>:<port>/
}
4.2.1 client定义
cadvisor/client/client.go
// Client represents the base URL for a cAdvisor client.
type Client struct {
baseUrl string
}
// NewClient returns a new v1.3 client with the specified base URL.
func NewClient(url string) (*Client, error) {
if !strings.HasSuffix(url, "/") {
url += "/"
}
return &Client{
baseUrl: fmt.Sprintf("%sapi/v1.3/", url),
}, nil
}
4.2.2. client方法
1)MachineInfo
// MachineInfo returns the JSON machine information for this client.
// A non-nil error result indicates a problem with obtaining
// the JSON machine information data.
func (self *Client) MachineInfo() (minfo *v1.MachineInfo, err error) {
u := self.machineInfoUrl()
ret := new(v1.MachineInfo)
if err = self.httpGetJsonData(ret, nil, u, "machine info"); err != nil {
return
}
minfo = ret
return
}
2)ContainerInfo
// ContainerInfo returns the JSON container information for the specified
// container and request.
func (self *Client) ContainerInfo(name string, query *v1.ContainerInfoRequest) (cinfo *v1.ContainerInfo, err error) {
u := self.containerInfoUrl(name)
ret := new(v1.ContainerInfo)
if err = self.httpGetJsonData(ret, query, u, fmt.Sprintf("container info for %q", name)); err != nil {
return
}
cinfo = ret
return
}
3)DockerContainer
// Returns the JSON container information for the specified
// Docker container and request.
func (self *Client) DockerContainer(name string, query *v1.ContainerInfoRequest) (cinfo v1.ContainerInfo, err error) {
u := self.dockerInfoUrl(name)
ret := make(map[string]v1.ContainerInfo)
if err = self.httpGetJsonData(&ret, query, u, fmt.Sprintf("Docker container info for %q", name)); err != nil {
return
}
if len(ret) != 1 {
err = fmt.Errorf("expected to only receive 1 Docker container: %+v", ret)
return
}
for _, cont := range ret {
cinfo = cont
}
return
}
4)AllDockerContainers
// Returns the JSON container information for all Docker containers.
func (self *Client) AllDockerContainers(query *v1.ContainerInfoRequest) (cinfo []v1.ContainerInfo, err error) {
u := self.dockerInfoUrl("/")
ret := make(map[string]v1.ContainerInfo)
if err = self.httpGetJsonData(&ret, query, u, "all Docker containers info"); err != nil {
return
}
cinfo = make([]v1.ContainerInfo, 0, len(ret))
for _, cont := range ret {
cinfo = append(cinfo, cont)
}
return
}
16.4 - Heapster介绍
1. heapster简介
Heapster是容器集群监控和性能分析工具,天然的支持Kubernetes和CoreOS。 Kubernetes有个出名的监控agent—cAdvisor。在每个kubernetes Node上都会运行cAdvisor,它会收集本机以及容器的监控数据(cpu,memory,filesystem,network,uptime)。
2. heapster部署与配置
2.1. 注意事项
需同步部署机器和被采集机器的时间:ntpdate time.windows.com
加入定时任务,定期同步时间
crontab –e
30 5 * * * /usr/sbin/ntpdate time.windows.com //每天早晨5点半执行
2.2. 容器式部署
#拉取镜像
docker pull heapster:latest
#运行容器
docker run -d -p 8082:8082 --net=host heapster:latest --source=kubernetes:http://<k8s-server-ip>:8080?inClusterConfig=false\&useServiceAccount=false --sink=influxdb:http://<influxdb-ip>:8086?db=<k8s_env_zone>
2.3. 配置说明
可以参考官方文档
2.3.1. –source
–source: 指定数据获取源。这里指定kube-apiserver即可。 后缀参数: inClusterConfig: kubeletPort: 指定kubelet的使用端口,默认10255 kubeletHttps: 是否使用https去连接kubelets(默认:false) apiVersion: 指定K8S的apiversion insecure: 是否使用安全证书(默认:false) auth: 安全认证 useServiceAccount: 是否使用K8S的安全令牌
2.3.2. –sink
–sink: 指定后端数据存储。这里指定influxdb数据库。 后缀参数: user: InfluxDB用户 pw: InfluxDB密码 db: 数据库名 secure: 安全连接到InfluxDB(默认:false) withfields: 使用InfluxDB fields(默认:false)。
3. Metrics
| 分类 | Metric Name | Description | 备注 |
|---|---|---|---|
| cpu | cpu/limit | CPU hard limit in millicores. | CPU上限 |
| cpu/node_capacity | Cpu capacity of a node. | Node节点的CPU容量 | |
| cpu/node_allocatable | Cpu allocatable of a node. | Node节点可分配的CPU | |
| cpu/node_reservation | Share of cpu that is reserved on the node allocatable. | ||
| cpu/node_utilization | CPU utilization as a share of node allocatable. | ||
| cpu/request | CPU request (the guaranteed amount of resources) in millicores. | ||
| cpu/usage | Cumulative CPU usage on all cores. | CPU总使用量 | |
| cpu/usage_rate | CPU usage on all cores in millicores. | ||
| filesystem | filesystem/usage | Total number of bytes consumed on a filesystem. | 文件系统的使用量 |
| filesystem/limit | The total size of filesystem in bytes. | 文件系统的使用上限 | |
| filesystem/available | The number of available bytes remaining in a the filesystem | 可用的文件系统容量 | |
| filesystem/inodes | The number of available inodes in a the filesystem | ||
| filesystem/inodes_free | The number of free inodes remaining in a the filesystem | ||
| memory | memory/limit | Memory hard limit in bytes. | 内存上限 |
| memory/major_page_faults | Number of major page faults. | ||
| memory/major_page_faults_rate | Number of major page faults per second. | ||
| memory/node_capacity | Memory capacity of a node. | ||
| memory/node_allocatable | Memory allocatable of a node. | ||
| memory/node_reservation | Share of memory that is reserved on the node allocatable. | ||
| memory/node_utilization | Memory utilization as a share of memory allocatable. | ||
| memory/page_faults | Number of page faults. | ||
| memory/page_faults_rate | Number of page faults per second. | ||
| memory/request | Memory request (the guaranteed amount of resources) in bytes. | ||
| memory/usage | Total memory usage. | ||
| memory/cache | Cache memory usage. | ||
| memory/rss | RSS memory usage. | ||
| memory/working_set | Total working set usage. Working set is the memory being used and not easily dropped by the kernel. | ||
| network | network/rx | Cumulative number of bytes received over the network. | |
| network/rx_errors | Cumulative number of errors while receiving over the network. | ||
| network/rx_errors_rate | Number of errors while receiving over the network per second. | ||
| network/rx_rate | Number of bytes received over the network per second. | ||
| network/tx | Cumulative number of bytes sent over the network | ||
| network/tx_errors | Cumulative number of errors while sending over the network | ||
| network/tx_errors_rate | Number of errors while sending over the network | ||
| network/tx_rate | Number of bytes sent over the network per second. | ||
| uptime | Number of milliseconds since the container was started. | - |
4. Labels
| Label Name | Description |
|---|---|
| pod_id | Unique ID of a Pod |
| pod_name | User-provided name of a Pod |
| pod_namespace | The namespace of a Pod |
| container_base_image | Base image for the container |
| container_name | User-provided name of the container or full cgroup name for system containers |
| host_id | Cloud-provider specified or user specified Identifier of a node |
| hostname | Hostname where the container ran |
| labels | Comma-separated(Default) list of user-provided labels. Format is 'key:value' |
| namespace_id | UID of the namespace of a Pod |
| resource_id | A unique identifier used to differentiate multiple metrics of the same type. e.x. Fs partitions under filesystem/usage |
5. heapster API
见官方文档:https://github.com/kubernetes/heapster/blob/master/docs/model.md
16.5 - Influxdb介绍
1. InfluxDB简介
InfluxDB是一个当下比较流行的时序数据库,InfluxDB使用 Go 语言编写,无需外部依赖,安装配置非常方便,适合构建大型分布式系统的监控系统。
主要特色功能:
1)基于时间序列,支持与时间有关的相关函数(如最大,最小,求和等)
2)可度量性:你可以实时对大量数据进行计算
3)基于事件:它支持任意的事件数据
2. InfluxDB安装
1)安装
wget https://dl.influxdata.com/influxdb/releases/influxdb-0.13.0.x86_64.rpm
yum localinstall influxdb-0.13.0.armhf.rpm
2)启动
service influxdb start
3)访问
http://服务器IP:8083
4)docker image方式安装
docker pull influxdb
docker run -d -p 8083:8083 -p 8086:8086 --expose 8090 --expose 8099 --volume=/opt/data/influxdb:/data --name influxsrv influxdb:latest
3. InfluxDB的基本概念
3.1. 与传统数据库中的名词做比较
| influxDB中的名词 | 传统数据库中的概念 |
|---|---|
| database | 数据库 |
| measurement | 数据库中的表 |
| points | 表里面的一行数据 |
3.2. InfluxDB中独有的概念
3.2.1. Point
Point由时间戳(time)、数据(field)、标签(tags)组成。
Point相当于传统数据库里的一行数据,如下表所示:
| Point属性 | 传统数据库中的概念 |
|---|---|
| time | 每个数据记录时间,是数据库中的主索引(会自动生成) |
| fields | 各种记录值(没有索引的属性)也就是记录的值:温度, 湿度 |
| tags | 各种有索引的属性:地区,海拔 |
3.2.2. series
所有在数据库中的数据,都需要通过图表来展示,而这个series表示这个表里面的数据,可以在图表上画成几条线:通过tags排列组合算出来
show series from cpu
4. InfluxDB的基本操作
InfluxDB提供三种操作方式:
1)客户端命令行方式
2)HTTP API接口
3)各语言API库
4.1. InfluxDB数据库操作
| 操作 | 命令 |
|---|---|
| 显示数据库 | show databases |
| 创建数据库 | create database db_name |
| 删除数据库 | drop database db_name |
| 使用某个数据库 | use db_name |
4.2. InfluxDB数据表操作
| 操作 | 命令 | 说明 |
|---|---|---|
| 显示所有表 | SHOW MEASUREMENTS | |
| 创建数据表 | insert table_name,hostname=server01 value=442221834240i 1435362189575692182 |
其中 disk_free 就是表名,hostname是索引,value=xx是记录值,记录值可以有多个,最后是指定的时间 |
| 删除数据表 | drop measurement table_name |
|
| 查看表内容 | select * from table_name |
|
| 查看series | show series from table_name |
series表示这个表里面的数据,可以在图表上画成几条线,series主要通过tags排列组合算出来 |
16.6 - Grafana部署
Docker部署
docker run -d -p 3000:3000 grafana/grafana:latest
K8S部署
helm部署
helm repo add grafana https://grafana.github.io/helm-charts
helm search repo grafana
参考:
17 - Serverless
17.1 - IaC
17.1.1 - Terraform的使用
1. 安装terraform
mac
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
linux: centos
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraform
2. terraform命令
编写terraform的部署文本,常用命令:
-
terraform init:项目初始化
-
terraform plan:预览
-
terraform apply:部署
-
terraform destroy:销毁
terraform --help
Usage: terraform [global options] <subcommand> [args]
The available commands for execution are listed below.
The primary workflow commands are given first, followed by
less common or more advanced commands.
Main commands:
init Prepare your working directory for other commands
validate Check whether the configuration is valid
plan Show changes required by the current configuration
apply Create or update infrastructure
destroy Destroy previously-created infrastructure
All other commands:
console Try Terraform expressions at an interactive command prompt
fmt Reformat your configuration in the standard style
force-unlock Release a stuck lock on the current workspace
get Install or upgrade remote Terraform modules
graph Generate a Graphviz graph of the steps in an operation
import Associate existing infrastructure with a Terraform resource
login Obtain and save credentials for a remote host
logout Remove locally-stored credentials for a remote host
metadata Metadata related commands
modules Show all declared modules in a working directory
output Show output values from your root module
providers Show the providers required for this configuration
refresh Update the state to match remote systems
show Show the current state or a saved plan
stacks Manage HCP Terraform stack operations
state Advanced state management
taint Mark a resource instance as not fully functional
test Execute integration tests for Terraform modules
untaint Remove the 'tainted' state from a resource instance
version Show the current Terraform version
workspace Workspace management
3. 通过terraform部署k8s应用
3.1. 创建部署文件
创建应用目录,并在本地创建main.tf文件。
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.0.0"
}
}
}
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_namespace" "test" {
metadata {
name = "nginx"
}
}
resource "kubernetes_deployment" "test" {
metadata {
name = "nginx"
namespace = kubernetes_namespace.test.metadata.0.name
}
spec {
replicas = 2
selector {
match_labels = {
app = "MyTestApp"
}
}
template {
metadata {
labels = {
app = "MyTestApp"
}
}
spec {
container {
image = "nginx"
name = "nginx-container"
port {
container_port = 80
}
}
}
}
}
}
resource "kubernetes_service" "test" {
metadata {
name = "nginx"
namespace = kubernetes_namespace.test.metadata.0.name
}
spec {
selector = {
app = kubernetes_deployment.test.spec.0.template.0.metadata.0.labels.app
}
type = "NodePort"
port {
node_port = 30201
port = 80
target_port = 80
}
}
}
3.2. 执行部署命令
- terraform init
初始化terraform provider数据。会在本地的.terraform目录下生成provider相关的二进制文件。
.terraform
└── providers
└── registry.terraform.io
└── hashicorp
└── kubernetes
└── 2.38.0
└── linux_amd64
├── LICENSE.txt
└── terraform-provider-kubernetes_v2.38.0_x5
- terraform plan
部署之前预览diff数据。
# terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# kubernetes_deployment.test will be created
+ resource "kubernetes_deployment" "test" {
+ id = (known after apply)
+ wait_for_rollout = true
+ metadata {
+ generation = (known after apply)
+ name = "nginx"
+ namespace = "nginx"
+ resource_version = (known after apply)
+ uid = (known after apply)
}
+ spec {
+ min_ready_seconds = 0
+ paused = false
+ progress_deadline_seconds = 600
+ replicas = "2"
+ revision_history_limit = 10
....
Plan: 3 to add, 0 to change, 0 to destroy.
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
- terraform apply
执行部署的操作。
# terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# kubernetes_deployment.test will be created
+ resource "kubernetes_deployment" "test" {
+ id = (known after apply)
+ wait_for_rollout = true
...
Plan: 3 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
kubernetes_namespace.test: Creating...
kubernetes_namespace.test: Creation complete after 0s [id=nginx]
kubernetes_deployment.test: Creating...
kubernetes_deployment.test: Creation complete after 4s [id=nginx/nginx]
kubernetes_service.test: Creating...
kubernetes_service.test: Creation complete after 0s [id=nginx/nginx]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
查看部署结果
terraform共创建了三个资源,即对应k8s的三个对象:namespace,deployment,service。
# kubectl get all -n nginx
NAME READY STATUS RESTARTS AGE
pod/nginx-658c56bc8-djhrx 1/1 Running 0 30m
pod/nginx-658c56bc8-mh67k 1/1 Running 0 30m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx NodePort 10.96.65.155 <none> 80:30201/TCP 30m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 2/2 2 2 30m
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-658c56bc8 2 2 2 30m
部署后会生成terraform.tfstate本地状态记录文件。
- terraform destroy
如果需要清理数据,则可以使用destroy命令清理。
4. 总结
本文以k8s平台为例展示了terraform的基本使用步骤,相比于kubectl和helm的k8s部署工具,terraform无根本区别。
-
terraform的优势在于以IaC(基础设施即代码)和GitOps的思想把将基础设施相关的运维部署操作以
声明式的方式记录在代码中,通过Git去管理期望的最终状态和版本迭代。 -
同时terraform可以对接绝大部分的基础设施,例如MySQL,Redis等,可以对接各个不同的部署平台和系统,例如阿里云,aws等,也可以针对公司内部的平台,单独写内部平台对接的provider,从而实现一套工具管理所有的基础设施。
参考:
17.1.2 - Terraform的基本概念
本文主要描述编写Terraform部署文件的基本概念。
1. 基本概念
1.1. 提供商(Provider)
Provider实现了每一种可配置的资源类型。可以理解为实现了资源增删改查的通用接口。
提供商通常定义在 providers.tf 文件中,您需要指定包含提供商定义的 Terraform 块。当声明了提供商后,Terraform 通过 init 命令自动下载提供商插件。
官方provider的仓库:https://registry.terraform.io/
terraform {
required_providers {
alicloud = {
source = "aliyun/alicloud"
version = "1.225.0"
}
}
}
provider "alicloud" {
# 配置你的阿里云凭据和地域信息
# 出于安全考虑,建议不要在这个文件中直接包含你的阿里云AccessKey和SecretKey,推荐使用环境变量或其它安全方式设置凭据:
# export ALICLOUD_ACCESS_KEY="<你的阿里云Access Key>"
# export ALICLOUD_SECRET_KEY="<你的阿里云Secret Key>"
region = "cn-hangzhou"
}
1.2. 资源(Resources)
资源(Resources)是定义基础设施组件的代码块,可以按照资源类型单独使用一个独立的 .tf 文件来存放,例如:database.tf。资源通过关键字 resource 来标识,之后是具体的资源类型和自定义名称。
resource "resource_type" "resource_name" {
# 指定资源类型的参数(Argument)
}
1.2.1. 属性参数引用
当从一个资源块访问另一个资源属性时,使用格式:<resource_type>.<resource_name>.<attribute>。
1.2.2. 资源定义考虑事项
-
resource_name 必须唯一。
-
resource_type 资源类型是与提供商关联的关键字,不能由用户自定义。
-
所有配置参数必须包含在资源块体内,即大括号之间。
-
所有必填参数必须设置。
1.3. 变量(Variables)
变量用于参数化你的 Terraform 配置。输入变量作为参数供给 Terraform 使用,使其允许在无需更改源配置代码的情况下轻松定制和共享配置。在 Terraform 运行时可以有多种不同的方式来设置其值:环境变量、CLI 参数、键值文件等。
变量必须在 variable 块中声明。建议将所有的变量声明保存在一个名为 variables.tf 的文件中。变量没有必选的参数,因此变量块可以为空,Terraform 可以根据变量值自动推断类型和默认值。
变量名称有两个设置规则:
-
变量名在模块内必须唯一
-
变量名不能是关键字,例如:count,for_each等
variable "variable_name" {
type=<变量类型>
description=<变量的描述>
default=<赋予变量的默认值>
sensitive=<是否是敏感信息>
validation=<变量值的验证规则>
}
1.3.1. type
type 参数指定了变量接受的值的类型。Terraform 支持以下基本变量类型:
-
bool,用于二进制值,如 true 或 false(不带引号)
-
number,用于数值变量
-
string,用于字符序列
1.3.2. default
要访问模块内声明的变量值,可以使用表达式 var.
resource "alicloud_vpc" "my_vpc" {
vpc_name = "main-vpc"
cidr_block = var.vpc_cidr_block
description = ""
}
variable "vpc_cidr_block" {
default = "10.0.0.0/16"
}
默认值可以通过分配值在环境变量或 .tfvars 文件或 -var 选项中被覆盖,如:
# 通过 CLI -var 选项覆盖变量默认值
$ terraform plan -var vpc_cidr_block="172.16.0.0/16"
1.3.3. sensitive
敏感值的可接受值为true。当设置为true时,变量的值在 terraform plan 或terraform apply 的输出中标记为敏感。
$ terraform plan
Terraform will perform the following actions:
# alicloud_vpc.my-vpc will be created
+ resource "alicloud_vpc" "my-vpc" {
+ cidr_block = (sensitive value) # 标记为敏感信息
+ create_time = (known after apply)
+ id = (known after apply)
+ status = (known after apply)
+ user_cidrs = (known after apply)
+ vpc_name = "main-vpc"
...
}
1.3.4. validation
验证块包含一个条件参数,用于指定验证规则。你可以通过在变量块中包含验证子块来验证赋给变量的值。
在如下示例中,length 和 substr 用作条件参数,验证 vpc_name 的值是否长度超过 4 并且是否以 tf- 为开头:
variable "vpc_name" {
validation {
condition = length(var.vpc_name) > 4 && substr(var.vpc_name, 0, 3) == "tf-"
error_message = "The vpc name must start with 'tf-' and be longer than 4 characters."
}
}
1.3.5. 变量的设置方式
# .tfvars 文件(推荐)
$ terraform apply -var-file my-vars.tfvars
# CLI 选项
$ terraform apply -var vpc_cidr_block=“172.16.0.0/16”
# 环境变量
$ export TF_VAR_vpc_dicr_block=“172.16.0.0/16”
$ terraform apply
# 默认的变量文件 terraform.tfvars
$ terraform apply
1.3.6. 变量的最佳实践
-
只参数化每个实例或环境中会变化的值
-
尽可能使用 .tfvars 变量文件
-
给变量起与其使用目的相关的名称
-
变量必须要有描述,增强 Terraform 配置文件的可读性和可维护性。
1.4. 输出(Outputs)
outputs.tf 文件保存了资源的输出值。由 Terraform 管理的每个资源实例都可以导出属性,这些属性的值可在 Terraform 配置的其他地方被引用。
output "vswitch_id" {
value = alicloud_vswitch.main_vswitch.id
}
1.5. 状态(State)
Terraform 在状态文件中保存它管理的资源的状态。默认情况下,状态文件是存储在本地,但它也可以远端存储。在团队协作场景中,远端存储通常是首选方法。
不要修改或触碰此文件,它是自动创建和更新的,一旦状态文件损坏,将会导致继续执行 Terraform 失败或者重复创建新的资源。
2. Terraform目录
-
根模块
根模块也被称为根配置文件,是运行 Terraform 命令的工作目录。
-
子模块
子模块是可选的,每个子模块可以是出于对某类复用功能的抽象,也可以是出于对根模块复杂逻辑的简化。根模块通过对子模块的引用来降低根模块的编写复杂度,提升复用性和可读性。
├── main.tf
├── modules
├── network
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── server
├── main.tf
├── variables.tf
└── outputs.tf
输出调用关系图
# install graphviz for mac
brew install graphviz
# 生成调用关系
terraform graph | dot -Tsvg > graph.svg
参考:
17.2 - knative介绍
1. knative简介
knative是一个将serverless的能力扩展到k8s中的开源项目。serverless让开发者无需关注容器、镜像、运维等事项,集中精力于开发代码本身,即将代码通过免运维的形式交付给serverless平台。代码会在设定的条件下运行,并自动实现扩缩容。
2. knative的组件
knative主要包含三个部分:
-
build: 将代码转换为容器,主要包括
- 将源代码从git仓库拉取下来,安装相关的依赖
- 构建容器镜像
- 将容器镜像推送到镜像仓库
-
Serving:创建一个可伸缩的部署。
- 配置定义了服务的状态,包括版本管理,每次修改都创建一个新版本部署,并保留旧版本。
- 灵活的路由控制,可以控制百分比的路由到新版本和旧版本服务。
- 自动弹性伸缩,可以快速创建上千个实例或快速调整实例数为0。
-
Eventing:事件触发,通过定义各种事件使用knative自动来完成这些任务,而无需手动编写脚本。
3. 部署knative
部署knative主要是部署Serving和Eventing两个组件,可以单独部署也可以同时部署。
3.1. 部署Serving
-
部署CRD
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.10.1/serving-crds.yaml -
部署Serving
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.10.1/serving-core.yaml -
部署HPA autoscaling(可选)
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.10.1/serving-hpa.yaml
查看部署结果
# kgdep -n knative-serving
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/activator 1/1 1 1 107s
deployment.apps/autoscaler 1/1 1 1 107s
deployment.apps/autoscaler-hpa 1/1 1 1 107s
deployment.apps/controller 1/1 1 1 107s
deployment.apps/domain-mapping 1/1 1 1 107s
deployment.apps/domainmapping-webhook 1/1 1 1 107s
deployment.apps/webhook 1/1 1 1 107s
3.2. 部署Eventing
-
部署CRD
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.10.0/eventing-crds.yaml -
部署Eventing
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.10.0/eventing-core.yaml
查看部署结果
# kgdep -n knative-eventing
NAME READY UP-TO-DATE AVAILABLE AGE
eventing-controller 1/1 1 1 2m13s
eventing-webhook 1/1 1 1 2m13s
pingsource-mt-adapter 0/0 0 0 2m13s
4. 部署knative客户端
wget https://github.com/knative/client/releases/download/knative-v1.10.0/kn-linux-amd64
chmod +x kn-linux-amd64
mv kn-linux-amd64 /usr/bin/kn
kn命令:
kn
kn is the command line interface for managing Knative Serving and Eventing resources
Find more information about Knative at: https://knative.dev
Serving Commands:
service Manage Knative services
revision Manage service revisions
route List and describe service routes
domain Manage domain mappings
container Manage service's containers (experimental)
Eventing Commands:
source Manage event sources
broker Manage message brokers
trigger Manage event triggers
channel Manage event channels
subscription Manage event subscriptions
eventtype Manage eventtypes
Other Commands:
plugin Manage kn plugins
secret Manage secrets
completion Output shell completion code
version Show the version of this client
5. 创建示例服务
以下通过yaml的方式演示。
vi hello.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello
spec:
template:
spec:
containers:
- image: ghcr.io/knative/helloworld-go:latest
ports:
- containerPort: 8080
env:
- name: TARGET
value: "World"
创建文件
kubectl apply -f hello.yaml
查看服务
# kubectl get ksvc
NAME URL LATESTCREATED LATESTREADY READY REASON
hello http://hello.default.svc.cluster.local hello-00001 hello-00001 Unknown IngressNotConfigured
# kubectl get po
NAME READY STATUS RESTARTS AGE
hello-00001-deployment-6469df75c-qpp5v 2/2 Running 0 15m
参考:
18 - 集群优化
18.1 - 调度优化
18.1.1 - 大规模Pod调度优化
假设在 Kubernetes 集群中一次性调度 1 万个 Pod, 这是一项极具挑战性的任务。如果管理不当,可能会导致调度器瓶颈、API Server 过载,甚至整个集群崩溃。
本文将探讨优化大规模 Pod 调度的最佳实践与技术手段。
🚀 面临的挑战
- 调度器压力大:大量 Pod 同时进入 Pending 状态,调度器处理不过来。
- API Server 压力大:高频的 CREATE/GET/LIST 请求可能触发限流。
- etcd 延迟增加:写入及状态变化频繁,导致存储后端压力过大。
- 节点压力不均衡:调度不均可导致部分节点 CPU/内存/磁盘 IO 资源打爆。
- 网络插件瓶颈:CNI 插件无法处理大量并发的 IP 分配。
1. 调度器优化
🔧 1.1. 控制 Pod 创建速率
不要一次性启动 10,000 个 Pod,而是:
- 分批创建:例如每批创建 500–1000 个 Pod。
- 控制速率:通过脚本或 Job 控制器引入
sleep等时间间隔。
示例 Shell 脚本:
for file in batches/batch_*.yaml; do
kubectl apply -f "$file"
sleep 10
done
⚙️ 1.2. 调优默认调度器
默认调度器(kube-scheduler)在高并发场景下可能成为瓶颈。为了高效调度 1 万个以上的 Pod,可以从以下几个方面进行深入调优:
✅ 1. 提高并发调度能力
Kubernetes v1.19+ 支持配置并行调度线程数:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
parallelism: 64
推荐值为 CPU 核心数的 2~4 倍(视调度密集度而定)。
注意:并发过高可能导致内存激增或 etcd 压力过大,建议结合压测评估。
✅ 2. 启用缓存调度器(Scheduling Queue 优化)
调度器内部维护了 Pending Pod 的优先队列(PriorityQueue)与 Node 信息缓存。
-
确保使用优先级调度(PodPriority)可帮助调度器优先处理重要任务。
-
配置调度器时可启用
permit插件阶段,在调度决策前提前控制调度流量。
✅ 3. 关闭或精简耗时插件
某些默认启用的插件在调度高峰时会带来性能负担:
| 插件 | 类型 | 说明 |
|---|---|---|
| VolumeBinding | Bind | 持久化卷绑定,需访问 API |
| NodeResourcesFit | Filter | 检查资源是否满足 |
| InterPodAffinity | Filter | Pod 之间亲和性计算复杂 |
⚠️ 优化建议:
-
无状态服务建议 关闭 VolumeBinding 插件。
-
只使用必要的 Score 插件(如
LeastAllocated、BalancedAllocation)。
配置方式:
plugins:
score:
disabled:
- name: "NodeResourcesBalancedAllocation"
enabled:
- name: "NodeResourcesLeastAllocated"
✅ 4. 预选节点集范围(节点剪枝)
调度器默认会评估所有可调度节点,1 万 Pod × 1 千节点的组合极其耗时。
优化方法:
-
NodeAffinity:提前通过标签筛掉不符合的节点。
-
使用 preFilter 插件 自定义节点集合。
示例:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/worker
operator: In
values:
- batch
✅ 5. 启用拓扑感知与亲和性缓存
使用拓扑调度建议:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
✅ 6. 控制调度队列压力(Backoff & Retry)
Pod 多次调度失败会进入 backoff 队列,默认退避时间为:
InitialBackoff = 1 * time.Second
MaxBackoff = 10 * time.Second
调大 MaxBackoff 可减缓高频重试对调度器的压力。
🧪 调优效果验证建议:
-
使用
--v=5级别运行kube-scheduler,输出调度详细日志。 -
观察调度延迟指标(SchedulingLatencySeconds):
-
framework_extension_point_duration_seconds -
scheduler_scheduling_duration_seconds_bucket
-
🧩 1.3. 扩展调度器(Scheduling Framework 插件)
Kubernetes 支持通过调度框架插件机制自定义调度逻辑。
✨ 示例:快速 Filter 插件
自定义过滤插件,可只评估部分节点,从而减少调度延迟:
func (f *FastFilterPlugin) Filter(...) *framework.Status {
if strings.HasPrefix(node.Name, "compute-node-") {
return framework.NewStatus(framework.Success)
}
return framework.NewStatus(framework.Unschedulable)
}
🧠 1.4. 使用多个调度器(调度器隔离)
并行部署多个调度器进程:
spec:
schedulerName: batch-scheduler
每类工作负载使用不同调度器进行处理,实现并行调度和资源隔离。
🛠 1.5. 使用高性能调度系统
Koordinator
- 支持批量调度、NUMA 感知、QoS 资源分级
- 与 Kubernetes 调度框架兼容,部署简单
Volcano
- 面向大规模批处理、AI/ML、HPC 任务调度
- 支持抢占、任务优先级、依赖关系等
📊 1.6. 监控与验证建议
- 使用
kubectl get pods -w实时观察 Pending 状态 - 关注调度事件
FailedScheduling - 跟踪 API Server 指标:QPS、延迟、内存占用
- 部署 Prometheus + Grafana 进行系统监控与可视化
✅ 1.7. 总结对比
| 优化方式 | 效果 |
|---|---|
| 控制 Pod 创建速率 | 避免控制面组件过载 |
| 提高调度器并发度 | 提升每秒调度吞吐 |
| 编写调度器插件 | 降低单次调度复杂度 |
| 多调度器架构 | 实现任务隔离与并行调度 |
| 使用 Koordinator/Volcano | 面向 AI/批处理等高负载场景 |
2. Etcd优化
etcd 是 Kubernetes 控制平面的核心存储引擎,一旦在大规模 Pod 创建、调度过程中出现 写入延迟增加,会直接影响 API Server 性能,进而拖慢调度器和控制器反应速度,甚至引发集群不可用。
🔧 2.1. 基础配置优化
✅ 1. 启用自动压缩历史数据
etcd 默认会保留历史版本,随着对象变化增多,存储膨胀,导致延迟升高。
--auto-compaction-retention=1h # 每小时清理历史
--snapshot-count=10000 # 控制何时触发快照
✅ 2. 启用 WAL 压缩
压缩 Write-Ahead Log,减少磁盘 I/O 开销:
--experimental-initial-corrupt-check=true
--experimental-compact-hash-check-enabled=true
💽 2.2. 硬件层优化(非常关键)
etcd 对 磁盘 IOPS 和延迟 敏感,推荐:
-
使用 SSD(NVMe 最佳)
-
etcd 独立磁盘,避免和 kubelet 或 container runtime 共用
-
提升内存(建议 16G+)、CPU 性能(至少 4 核)
-
开启 NUMA 亲和配置,减少跨核调度
🧱 2.3. 集群部署架构优化
✅ 1. 隔离部署 etcd
etcd不要与 kube-apiserver、controller-manager 等组件共节点运行。
1)etcd 对磁盘 IO、内存和 CPU 的性能非常敏感,特别是磁盘延迟对 etcd 性能和稳定性有显著影响。
-
kube-apiserver、controller-manager、scheduler 等组件也会频繁访问 etcd,产生较大 CPU 和内存负载。
-
如果它们部署在同一节点,容易导致 资源竞争(尤其是 IO),影响 etcd 的响应能力和稳定性,进而影响整个集群的控制面。
2) Kubernetes 通常运行在高速局域网内,访问 etcd 的延迟很小
-
网络延迟在现代数据中心或云环境中通常是微秒到毫秒级别。
-
只要保证 etcd 集群网络稳定,控制面组件即使不在同一个节点也能快速访问。
3) 不在一个节点,可以避免“局部高负载”导致连锁影响
-
如果 kube-apiserver 跟 etcd 同节点,一旦 kube-apiserver 突发流量(比如创建大量资源),会导致 etcd 所在节点资源被占满,从而影响 etcd 响应。
-
反之亦然,etcd 的 GC 或 compaction 操作也可能影响 apiserver 的性能。
✅ 2. 多副本部署(3~5个节点)
避免单点瓶颈,并启用高可用。
3. kube-apiserver优化
在大量 Pod 同时调度时,API Server 压力大 是造成集群卡顿或异常的核心瓶颈之一,主要表现为:
-
创建、更新、查询 Pod 等请求响应变慢
-
kubelet、controller-manager 与 API Server 通信超时
-
etcd QPS 激增、延迟升高,甚至触发熔断
以下是具体的优化策略,从集群参数、限流、组件解耦等多个层面展开:
🧱 3.1. 控制请求速率(限流)
✅ 1. 控制客户端创建速率
比如大量 Job/Deployment 控制器、脚本同时发出 kubectl apply 请求:
解决方法:
-
采用
kubectl --wait=false异步创建 -
使用分批 apply 或 sleep 控制速率
-
使用 controller(例如自定义 CRD + controller)分批分组管理 pod/job
⚙️ 3.2. 调优 API Server 参数
在 kube-apiserver 启动参数中:
✅ 1. 增加最大并发 QPS
--max-requests-inflight=4000 # 默认 400,增加吞吐能力
--max-mutating-requests-inflight=2000 # 默认 200,调大写请求容量
✅ 2. 增加缓存时间与响应窗口
--request-timeout=1m
--min-request-timeout=300
3.3. operator优化
如果有开发自定义的operator,则需要对operator的逻辑进行优化。
✅ 1. 使用 informer 缓存机制(client-go 默认支持)
自定义控制器或调度插件中应使用共享缓存,而非频繁 GET:
informer := factory.Core().V1().Pods().Informer()
✅ 2. 减少不必要的 Watch 或频繁 List 请求
-
调度器插件中不要频繁访问 Pod 列表
-
减少 metrics 或审计日志系统对 API 的高频采集
4. 网络插件优化
Kubernetes 网络插件(CNI)在大规模部署或高并发场景下,若处理能力跟不上,会出现 调度成功但网络不通、服务连接慢、跨节点通信异常 等问题。
🧭 4.1. 网络瓶颈表现
| 现象 | 可能原因 |
|---|---|
Pod 创建卡住在 ContainerCreating |
CNI 插件调用超时,网络设备未初始化 |
| 跨节点服务访问慢或超时 | 网络插件转发路径性能不足,iptables/ebpf 累积 |
| 集群中 ping 某些 Pod 慢 | 某些节点流量瓶颈,或者 VXLAN 隧道高延迟 |
kube-proxy CPU 占满 |
iptables 规则过多或频繁变更 |
✅ 4.2. 选型优化:选择高性能 CNI 插件
| 插件 | 性能特点 |
|---|---|
| Cilium | eBPF 驱动,无需 iptables,极高性能,支持大规模节点 |
| Calico (BPF 模式) | 支持 eBPF 模式,性能更好于传统 iptables |
| Flannel | 适用于小规模集群,性能普通,不推荐大集群使用 |
| Multus | 支持多网卡/多 CNI,适合边缘场景但调试复杂 |
💡 推荐使用 Cilium 或 Calico(eBPF 模式),避免使用传统 Flannel/VXLAN。
⚙️ 4.3. 网络插件参数调优
🔹 Cilium 示例
配置 /etc/cilium/cilium-config:
enable-bpf-masquerade: "true"
enable-ipv4-masquerade: "false"
bpf-lb-map-max: "65536"
bpf-ct-global-tcp-max: "524288"
bpf-ct-global-any-max: "262144"
并启用 kube-proxy 替代模式(kube-proxy-free):
kubeProxyReplacement: "strict"
🔄 4.4. 跨节点通信优化
✅ 1. 减少 VXLAN 封装(或改为 Native Routing)
-
Flannel/Calico VXLAN 模式性能差
-
推荐切换为 Calico 的 BGP 模式(路由直达,无封装)
✅ 2. 使用 Direct Server Return(DSR)+ ECMP 路由
大流量服务部署时,避免中心化转发。
🔃 4.5. kube-proxy 优化
✅ 1. 使用 ipvs 模式代替 iptables
--proxy-mode=ipvs
--ipvs-scheduler=rr
相比 iptables,ipvs 处理服务转发在大规模集群下 CPU 更省、延迟更低。
✅ 2. 配合 eBPF 替代 kube-proxy(Cilium 推荐)
Cilium 的 kube-proxy-replacement=strict 直接使用 BPF 加速服务调度。
🔧 4.6. 节点系统参数优化
设置节点的内核参数,提升大流量下系统处理能力:
# 提高 conntrack 表容量
sysctl -w net.netfilter.nf_conntrack_max=262144
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=86400
# 允许更多文件描述符
ulimit -n 1048576
# 调高队列长度
sysctl -w net.core.somaxconn=1024
sysctl -w net.core.netdev_max_backlog=250000
📊 4.7. 监控关键指标
通过 Cilium/Calico 自带 metrics 或 Prometheus 采集:
| 指标 | 说明 |
|---|---|
cilium_forwarding_latency_seconds |
转发延迟 |
cilium_drop_count_total |
数据包被丢弃的原因 |
iptables_rule_count |
kube-proxy 中规则数量 |
conntrack_entries |
当前连接跟踪表大小 |
✅ 4.8. 总结优化建议表
| 方向 | 方案 |
|---|---|
| 插件选型 | 使用 Cilium/Calico eBPF,避免 Flannel |
| 插件配置 | 优化转发表、连接跟踪表大小 |
| 网络架构 | BGP 替代 VXLAN,开启 kube-proxy-free |
| 系统内核 | 调高 conntrack / backlog 等参数 |
| 转发模式 | 使用 ipvs 或 eBPF 加速 kube-proxy |
| 监控排查 | 开启 drop 分析、BPF 路径追踪 |
5. 总结
大规模 Pod 调度不仅仅是追求速度,更重要的是在高压下保持系统的稳定性与正确性。需要从各个方面进行集群优化才能承受大规模pod集群的性能压力。本文分别从以下几个方面进行优化:
-
调度器及扩展调度器
-
ETCD优化
-
kube-apiserver优化
-
网络插件及节点优化
只要设计合理,Kubernetes 完全可以稳定高效地调度数万个 Pod。
18.1.2 - Volcano的使用
本文主要介绍volcano的使用,内容由官网文档进行整理。
1. Volcano介绍
Volcano作为一个通用批处理平台,Volcano与几乎所有的主流计算框 架无缝对接,如Spark 、TensorFlow 、PyTorch 、 Flink 、Argo 、MindSpore 、 PaddlePaddle,Ray等。还提供了包括异构设备调度,网络拓扑感知调度,多集群调度,在离线混部调度等多种调度能力。
2. 特性
2.1. 统一调度
- 支持Kubernetes原生负载调度
- 支持使用VolcanoJob来进行PyTorch、TensorFlow、Spark、Flink、Ray等框架的一体化作业调度
- 将在线微服务和离线批处理作业统一调度,提升集群资源利用率
2.2. 丰富的调度策略
- Gang Scheduling:确保作业的所有任务同时启动,适用于分布式训练、大数据等场景。
- Binpack Scheduling:通过任务紧凑分配优化资源利用率
- Heterogeneous device scheduling:高效共享GPU异构资源,支持CUDA和MIG两种模式的GPU调度,支持NPU调度
- Proportion/Capacity Scheduling:基于队列配额进行资源的共享/抢占/回收
- NodeGroup Scheduling:支持节点分组亲和性调度,实现队列与节点组的绑定关系
- DRF Scheduling:支持多维度资源的公平调度
- SLA Scheduling:基于服务质量的调度保障
- Task-topology Scheduling:支持任务拓扑感知调度,优化通信密集型应用性能
- NUMA Aware Scheduling:支持NUMA架构的调度,优化任务在多核处理器上的资源分配,提升内存访问效率和计算性能
2.3. GPU支持
支持多种GPU虚拟化技术,提供灵活的GPU资源管理
- 动态MIG支持:支持NVIDIA Multi-Instance GPU(MIG),通过硬件级隔离将单个GPU分割为多个独立的GPU实例
- vCUDA虚拟化:通过软件层将物理GPU虚拟化为多个vGPU设备,实现资源共享和隔离
- 细粒度资源控制:为每个GPU实例提供独立的显存和算力分配
- 多容器共享:允许多个容器安全地共享同一个GPU资源,提升利用率
- 统一监控:提供对所有GPU实例的统一使用情况监控和指标收集
2.4. 在离线混部
- 支持在线和离线业务混合部署,通过统一调度,动态资源超卖,CPU Burst,资源隔离等能力,提升资源利用率的同时保障在线业务QoS
2.5. 重调度
- 支持动态重调度,优化集群负载分布,提升系统稳定性
3. 架构介绍

Volcano由scheduler、controllermanager、admission和vcctl组成:
-
Scheduler Volcano scheduler通过一系列的action和plugin调度Job,并为它找到一个最适合的节点。与Kubernetes default-scheduler相比,Volcano与众不同的 地方是它支持针对Job的多种调度算法。
-
Controllermanager Volcano controllermanager管理CRD资源的生命周期。它主要由Queue ControllerManager、 PodGroupControllerManager、 VCJob ControllerManager构成。
-
Admission Volcano admission负责对CRD API资源进行校验。
-
Vcctl 是Volcano的命令行客户端工具。
4. 部署
可以通过helm部署volcano。
helm repo add volcano-sh https://volcano-sh.github.io/helm-charts
helm repo update
helm install volcano volcano-sh/volcano -n volcano-system --create-namespace
查看volcano组件
kubectl get all -n volcano-system
NAME READY STATUS RESTARTS AGE
pod/volcano-admission-5bd5756f79-p89tx 1/1 Running 0 6m10s
pod/volcano-admission-init-d4dns 0/1 Completed 0 6m10s
pod/volcano-controllers-687948d9c8-bd28m 1/1 Running 0 6m10s
pod/volcano-scheduler-94998fc64-9df5g 1/1 Running 0 6m10s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/volcano-admission-service ClusterIP 10.96.140.22 <none> 443/TCP 6m10s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/volcano-admission 1/1 1 1 6m10s
deployment.apps/volcano-controllers 1/1 1 1 6m10s
deployment.apps/volcano-scheduler 1/1 1 1 6m10s
NAME DESIRED CURRENT READY AGE
replicaset.apps/volcano-admission-5bd5756f79 1 1 1 6m10s
replicaset.apps/volcano-controllers-687948d9c8 1 1 1 6m10s
replicaset.apps/volcano-scheduler-94998fc64 1 1 1 6m10s
NAME COMPLETIONS DURATION AGE
job.batch/volcano-admission-init 1/1 28s 6m10s
5. 使用
我们以组调度为例(gang scheduling),通过成组调度,您可以指定一个最小数量的Pod,这些Pod必须能够作为一个组被同时调度,然后该工作负载的任何Pod才能启动。
5.1. 创建带有group-min-member注解的Deployment
让我们创建一个Deployment,它期望有3个副本,但要求至少有2个Pod能被Volcano作为一个组进行调度。
# deployment-with-minmember.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
annotations:
# 对成组调度至关重要:此注解告知Volcano将此Deployment视为一个组,
# 要求至少2个Pod能够一起调度,然后才会启动任何Pod。
scheduling.volcano.sh/group-min-member: "2"
# 可选:您也可以为此Deployment创建的PodGroup指定一个特定的Volcano队列。
# scheduling.volcano.sh/queue-name: "my-deployment-queue"
labels:
app: my-app
spec:
replicas: 3 # 我们期望应用有3个副本
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
schedulerName: volcano # 关键:确保此Deployment的Pod使用Volcano调度器
containers:
- name: my-container
image: busybox
command: ["sh", "-c", "echo 'Hello Volcano from Deployment'; sleep 3600"] # 一个长时间运行的命令,用于演示
resources:
requests:
cpu: 1
limits:
cpu: 1
5.2. 观察自动创建的PodGroup和Pod
当您应用带有scheduling.volcano.sh/group-min-member注解的Deployment(或StatefulSet)时,Volcano会自动创建一个PodGroup资源。此PodGroup负责为属于该工作负载的Pod强制执行成组调度约束。
检查PodGroup的状态:
kubectl get pg podgroup-[ReplicaSet的UID] -oyaml
输出
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
# ...
name: podgroup-09e95eb0-e520-4b50-a15c-c14cad844674
namespace: default
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: ReplicaSet
name: my-app-deployment-74644c8849
uid: 09e95eb0-e520-4b50-a15c-c14cad844674
# ...
spec:
minMember: 2
minResources:
count/pods: "2"
cpu: "2"
limits.cpu: "2"
pods: "2"
requests.cpu: "2"
queue: default
status:
conditions:
- lastTransitionTime: "2025-05-28T09:08:13Z"
reason: tasks in gang are ready to be scheduled
status: "True"
transitionID: e0b1508e-4b77-4dea-836f-0b14f9ca58df
type: Scheduled
phase: Running
running: 3
将观察到Volcano调度器会确保至少minMember(本例中为2)个Pod能够一起调度,然后才允许此Deployment中的任何Pod启动。如果资源不足以满足这些Pod的需求,它们将保持Pending状态。
参考:
18.2 - GPU
18.2.1 - k8s管理GPU容器
本文主要描述如何在k8s中管理GPU的节点和容器。
Kubernetes(K8s)对 GPU 的支持,适合用于机器学习、深度学习、图像处理等高性能计算场景。
1. 实现思路
K8s 本身不直接管理 GPU,而是通过 NVIDIA 的 GPU 设备插件(NVIDIA Device Plugin) 将 GPU 资源暴露给容器。整个流程如下:
物理 GPU → 安装驱动 + 宿主机工具 → 容器 runtime(如 containerd)→ Kubernetes 通过 device plugin 管理 → Pod 使用 GPU
2. 实现步骤
2.1. 节点准备(仅 GPU 节点需要)
运行 NVIDIA 设备插件的先决条件如下:
- NVIDIA 驱动程序 ~= 384.81
- nvidia-docker >= 2.0 || nvidia-container-toolkit >= 1.7.0(>= 1.11.0 在基于 Tegra 的系统上使用集成 GPU)
- nvidia-container-runtime 配置为默认低级运行时
- Kubernetes 版本 >= 1.10
安装 NVIDIA 官方驱动。
# 示例安装命令(Ubuntu)
sudo apt install nvidia-driver-525
nvidia-smi # 验证 GPU 可用
2.2. 安装容器运行时插件(containerd 支持)
安装 NVIDIA Container Toolkit,参考Installing the NVIDIA Container Toolkit
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
export NVIDIA_CONTAINER_TOOLKIT_VERSION=1.17.8-1
sudo apt-get install -y \
nvidia-container-toolkit=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
nvidia-container-toolkit-base=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
libnvidia-container-tools=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
libnvidia-container1=${NVIDIA_CONTAINER_TOOLKIT_VERSION}
配置containerd
nvidia-ctk会修改containerd的配置使得containerd可以使用NVIDIA Container Runtime。
sudo nvidia-ctk runtime configure --runtime=containerd
sudo systemctl restart containerd
2.3. 安装 NVIDIA Device Plugin(K8s 插件)
NVIDIA device plugin 通过k8s daemonset的方式部署到每个k8s的node节点上,实现了Kubernetes device plugin的接口。
提供以下功能:
- 暴露每个节点的GPU数量给集群
- 跟踪GPU的健康情况
- 使在k8s的节点可以运行GPU容器
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.17.1/deployments/static/nvidia-device-plugin.yml
-
它会在每个 GPU 节点启动一个
nvidia-device-pluginDaemonSet。 -
它会向节点注册一个
nvidia.com/gpu资源。
2.4. 编写使用 GPU 的 Pod/Deployment
部署守护进程后,容器现在可以使用以下nvidia.com/gpu资源类型请求 NVIDIA GPU:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
restartPolicy: Never
containers:
- name: cuda-container
image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0
resources:
limits:
nvidia.com/gpu: 1 # requesting 1 GPU
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
EOF
3. nvidia-device-plugin内容
nvidia-device-plugin的daemonset yaml文件如下,具体参考:nvidia-device-plugin.yml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nvidia-device-plugin-daemonset
namespace: kube-system
spec:
selector:
matchLabels:
name: nvidia-device-plugin-ds
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
name: nvidia-device-plugin-ds
spec:
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
# Mark this pod as a critical add-on; when enabled, the critical add-on
# scheduler reserves resources for critical add-on pods so that they can
# be rescheduled after a failure.
# See https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/
priorityClassName: "system-node-critical"
containers:
- image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
name: nvidia-device-plugin-ctr
env:
- name: FAIL_ON_INIT_ERROR
value: "false"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
4. 构建和运行nvidia-device-plugin
4.1. docker方式
4.1.1. 编译
- 直接拉取dockerhub的镜像
$ docker pull nvidia/k8s-device-plugin:1.0.0-beta4
- 拉取代码构建镜像
$ docker build -t nvidia/k8s-device-plugin:1.0.0-beta4 https://github.com/NVIDIA/k8s-device-plugin.git#1.0.0-beta4
- 修改nvidia-device-plugin后构建镜像
$ git clone https://github.com/NVIDIA/k8s-device-plugin.git && cd k8s-device-plugin
$ git checkout 1.0.0-beta4
$ docker build -t nvidia/k8s-device-plugin:1.0.0-beta4 .
4.1.2. 运行
- docker本地运行
$ docker run --security-opt=no-new-privileges --cap-drop=ALL --network=none -it -v /var/lib/kubelet/device-plugins:/var/lib/kubelet/device-plugins nvidia/k8s-device-plugin:1.0.0-beta4
- daemonset运行
$ kubectl create -f nvidia-device-plugin.yml
4.2. 非docker方式
4.2.1. 编译
$ C_INCLUDE_PATH=/usr/local/cuda/include LIBRARY_PATH=/usr/local/cuda/lib64 go build
4.2.2. 本地运行
$ ./k8s-device-plugin
参考:
18.2.2 - Volcano GPU虚拟化
本文主要描述如何通过volcano实现GPU资源的虚拟化。基于volcano官方文档整理。
1. 背景
随着大模型和AI的发展,GPU算力的需求越来越高,但是GPU成本高昂,对于小型工作负载,单个GPU可能造成资源浪费;而对于大型工作负载,单个GPU的算力又可能未被充分挖掘。因此需要通过GPU虚拟化的技术来提高GPU的利用率。
2. Volcano虚拟化方式
Volcano主要支持硬件和软件两种GPU共享模式,用以实现vGPU调度并满足不同的硬件能力与性能需求:
2.1. HAMI-core(基于软件的vGPU)
描述: 通过VCUDA (一种CUDA API劫持技术) 对GPU核心与显存的使用进行限制,从而实现软件层面的虚拟GPU切片。
使用场景: 适用于需要细粒度GPU共享的场景,兼容所有类型的GPU。
2.2. Dynamic MIG(硬件级GPU切片)
描述: 采用NVIDIA的MIG (Multi-Instance GPU)技术,可将单个物理GPU分割为多个具备硬件级性能保障的隔离实例。
使用场景: 尤其适用于对性能敏感的工作负载,要求GPU支持MIG特性(如A100、H100系列)。
2.3. 对比
| 模式 | 隔离级别 | 是否依赖MIG GPU | 需注解指定模式 | 核心/显存控制方式 | 推荐应用场景 |
|---|---|---|---|---|---|
| HAMI-core | 软件 (VCUDA) | 否 | 否 | 用户自定义 (核心/显存) | 通用型工作负载 |
| Dynamic MIG | 硬件 (MIG) | 是 | 是 | MIG实例规格决定 | 对性能敏感的工作负载 |
3. 部署
1、 部署volcano,参考:Volcano的使用
2、 部署volcano-vgpu-device-plugin。
该组件是一个DaemonSet。
wget https://raw.githubusercontent.com/Project-HAMi/volcano-vgpu-device-plugin/refs/heads/main/volcano-vgpu-device-plugin.yml
kubectl apply -f volcano-vgpu-device-plugin.yml
更改调度器配置:
kind: ConfigMap
apiVersion: v1
metadata:
name: volcano-scheduler-configmap
namespace: volcano-system
data:
volcano-scheduler.conf: |
actions: "enqueue, allocate, backfill"
tiers:
- plugins:
- name: predicates
- name: deviceshare
arguments:
deviceshare.VGPUEnable: true # 启用vgpu插件
deviceshare.SchedulePolicy: binpack # 调度策略:binpack / spread
验证部署
# kubectl get node {node-name} -o yaml
# 查看是否有上报GPU资源
volcano.sh/vgpu-memory: "89424"
volcano.sh/vgpu-number: "8"
4. 使用
通过volcano.sh/vgpu-mode: "hami-core"来选择使用哪种虚拟化方式。
4.1. HAMI-core使用方法
pod yaml
kind: Pod
apiVersion: v1
metadata:
name: hami-pod
annotations:
volcano.sh/vgpu-mode: "hami-core" # 指定模式
spec:
schedulerName: volcano
containers:
- name: cuda-container
image: nvidia/cuda:9.0-devel
resources:
limits:
volcano.sh/vgpu-number: 1 # 请求1张GPU卡
volcano.sh/vgpu-cores: 50 # (可选)每个vGPU使用50%核心
volcano.sh/vgpu-memory: 3000 # (可选)每个vGPU使用3G显存
4.2. Dynamic MIG使用方法
若需启用MIG (Multi-Instance GPU)模式,请在目标GPU节点上执行以下命令:
sudo nvidia-smi -mig 1
- MIG实例规格配置(可选):
volcano-vgpu-device-plugin会自动生成一套初始MIG配置,并存储于kube-system命名空间下的volcano-vgpu-device-config ConfigMap中。用户可按需自定义此配置。更多详情请参阅vgpu设备插件YAML文件。
- 带MIG注解的Pod配置示例:
kind: Pod
apiVersion: v1
metadata:
name: mig-pod
annotations:
volcano.sh/vgpu-mode: "mig"
spec:
schedulerName: volcano
containers:
- name: cuda-container
image: nvidia/cuda:9.0-devel
resources:
limits:
volcano.sh/vgpu-number: 1
volcano.sh/vgpu-memory: 3000
注意:实际分配的显存大小取决于最匹配的MIG实例规格(例如:请求3GB显存,可能会分配到规格为5GB的MIG实例)。
参考: